From: Lubomir Sedlacik Date: Tue, 6 Nov 2001 23:22:29 +0000 (+0000) Subject: addition of silc.css X-Git-Tag: silcertest~25 X-Git-Url: http://git.silcnet.org/gitweb/?p=silc.git;a=commitdiff_plain;h=2dc218143c7859f7529396dc121ae08e2fd78da0;hp=23c5df1c8b0bfe539d3fa65802186e6e09e044aa addition of silc.css --- diff --git a/CHANGES b/CHANGES new file mode 100644 index 00000000..fe6c1481 --- /dev/null +++ b/CHANGES @@ -0,0 +1,5939 @@ +Tue Nov 6 21:31:54 EET 2001 Pekka Riikonen + + * Added silc_hash_babbleprint to create a Bubble Babble + Encoded fingerprint. The encoding is developed by Antti + Huima (draft-huima-babble-01.txt), and it creates human + readable strings out of binary data. Affected file + lib/silccrypt/silchash.[ch]. + + * Print the babble print now in addition of fingerprint as well + in Irssi SILC client. Affected files are + irssi/src/fe-common/silc/module-formats.[ch], + irssi/src/fe-common/silc/core/client_ops.c. + +Sun Nov 4 23:37:28 EET 2001 Pekka Riikonen + + * Fixed a security problem found in SKE. The initiator's + public key too is now added to the HASH hash value creation + which is signed by the responder to create the SIGN value. + This will prevent anyone in the middle to lie to the responder + about the initiator's public key. If this is done now, the + man in the middle will get caught. Updated the protocol + specification. + +Sun Nov 4 11:43:53 EET 2001 Pekka Riikonen + + * Better installation directory handling. Configure module + paths and other paths automatically to example_silc* files + in doc/. A patch by toma. + + * Fixed compiler warning from MPI library, and from SILC RNG. + A patch by johnny. + + * Added SILC_SERVER_PID_FILE to define the pid file for server. + It can be configured with ./configure. A patch by toma. + +Sat Nov 3 23:48:23 EET 2001 Pekka Riikonen + + * Find correct make to use in prepare-clean. A patch by + toma. Affected file prepare-clean. + +Sat Nov 3 22:04:00 PST 2001 Brian Costello + + * Added irssi variables use_auto_addr, auto_bind_ip, + auto_bind_port and auto_public_ip. + + * Changed the interface for silc_client_send_key_agreement + in lib/silcclient/silcapi.h + + Affected files: + + irssi/src/silc/core/silc-core.c + irssi/config + lib/silcclient/silcapi.h + irssi/src/silc/core/silc-channels.c + lib/silcclient/client_keyagr.c + irssi/docs/help/key + +Sat Nov 3 17:48:55 EET 2001 Pekka Riikonen + + * Added silc_pkcs_public_key_compare to compare two + public keys. Affected file lib/silccrypt/silcpkcs.[ch]. + + * Check that the client who set the founder mode on the + channel is the same client that is giving the founder + mode to itself. It is done by comparing the saved public + key (it is saved even in the authentication is passphrase). + Affected file silcd/command.c. + +Fri Nov 2 18:52:08 EST 2001 Pekka Riikonen + + * Do not process packet for disconnected socket connection. + Affected file lib/silccore/silcpacket.c. + + * Process the DISCONNECT packet through scheduler in the + client library. Affected file lib/silcclient/client.c. + + * Fixed the silc_client_packet_parse to not to increase + the packet sequence number if the conn->sock and the + current socket connection is not same. This can happen + for example during key agreement when the conn includes + multiple socket connections (listeners). Affected file + lib/silcclient/client.c. + + * The sender of the file transfer request now provides also + the pointer (listener) for the key exchange protocol. If + the listener cannot be created then it sends empty key + agreement and lets the receiver provide the listener. + + Added `local_ip' and `local_port' arguments to the + silc_client_file_send. If they are provided they are used, + if not then it will attempt to find local IP address, if + not found or bind fails then the remote client will provide + the listener. + + Affected files are lib/silcclient/client_ftp.c and + lib/silcclient/silcapi.h. + + * Extended the FILE SEND command to support defining the + local IP and port for key exchange listener. They are + optional. Affected file irssi/src/silc/core/silc-servers.c. + +Thu Nov 1 22:10:07 EST 2001 Pekka Riikonen + + * Defined to WHOIS command reply the sending of fingerprint + of the client's public key (if the proof of posession of the + corresponding private key is verified by the server). + Updated to the protocol specification. + + * Added support of receiving the client's public key's + fingerprint in command reply in client library. Affected + file is lib/silcclient/command_reply.c, and + lib/silcclient/idlist.[ch]. + +Thu Nov 1 18:06:12 EST 2001 Pekka Riikonen + + * Do not send over 128 chars long nickname to the server + in NICK command. Affected file lib/silcclient/command.c. + + * Do not send over 256 chars long channel names to the server + in JOIN command. Affected file lib/silcclient/command.c. + +Tue Oct 30 22:48:59 EST 2001 Pekka Riikonen + + * Assure that silc_server_close_connection cannot be called + twice for same socket context. Affected file is + silcd/server.c. + +Tue Oct 30 16:58:14 EST 2001 Pekka Riikonen + + * Send error message to application if opening file for + writing during file transfer fails. Affected file is + lib/silcclient/client_ftp.c. + + Remove all file transfer sessions for a client that we're + removing from ID cache. + + Affected file is lib/silcclient/client_ftp.c. + + * Fixed silc_net_addr2bin to return correct address. Affected + file lib/silcutil/[unix/win32]/silc[unix/win32]net.c. + + * Fixed file transfer session removing on signoff notify. + Affected file irssi/src/silc/core/silc-servers.c. + + * Added the SilcClientFileError to be returned in the monitor + callback. Added NO_SUCH_FILE and PERMISSION_DENIED errors. + Affected file lib/silcclient/silcapi.h. + +Mon Oct 29 17:43:04 EST 2001 Pekka Riikonen + + * Fixed a crash in silc_client_ftp_free_sessions and + silc_client_ftp_session_free_client. Affected file + lib/silcclient/client_ftp.c. + + * Added `disabled' field in the SilcChannelEntry in the server + to indicate if the server entry is disabled. Affected file + silcd/idlist.h, silcd/command[_reply].c. + + * SILC server adds now /var/run/silcd.pid everytime it is + started. Affected file silcd/silcd.c. + + * Added silc_server_packet_send_clients to send a packet to + the provided table of client entries. Affected file + silcd/packet_send.[ch]. + + * Fixed a crash in client resolving in client_prvmsg.c in + client library. Affected file lib/silcclient/client_prvmsg.c. + + * Do not actually remove the client directly from ID cache + during SERVER_SIGNOFF, but invalidate it. This way we + preserve the WHOWAS info for the client. Affected file + silcd/server_util.c. + + * Fixed SERVER_SIGNOFF notify handling in the server. The + server is now able to process incoming SERVER_SIGNOFF notify + for a server that it doesn't even know about. It will remove + the clients provided in the notify. Affected file + silcd/packet_receive.c. + + * Check for partial packet in data queue after every packet that + was found from the queue. Return and wait for more data if + there is partial data in queue. Affected file is + lib/silccore/silcpacket.c. + +Sun Oct 28 18:46:27 EST 2001 Pekka Riikonen + + * Added SilcClietFileError enum to indicate error in + file transfer. Added SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT + and SILC_CLIENT_FILE_MONITOR_ERROR new monitor statuses. + Affected files lib/silcclient/silcapi.h and + lib/silcclient/client_ftp.c. + + * Check that newsize in silc_buffer_realloc is larger than + the old buffer's size. Affected file lib/silcutil/silcbufutil.h. + + * Added better monitor of file transfers. It now monitors + key agreement protocol during the file transfer too. Added + error reporting too. Affected files + irssi/src/silc/core/silc-servers.c, + irssi/src/fe-common/silc/module-formats.[ch]. + + * Wrote a help file for FILE command. + + * Added silc_rng_global_get_byte_fast to get not-so-secure + random data as fast as possible. Random data is read from + /dev/urandom if available and from the SILC RNG if not + available. It is used in padding generation. Affected file + lib/silccrypt/silcrng.[ch]. + + * All packets in client library are now processed synchronously. + Optimized packet processing a lot. Affected file + lib/silcclient/client.c. + + * All server connection packets are processing synchronously + now in server, to optimize packet processing. Affected file + silcd/server.c. + + * Include files are installed now only in Toolkit distribution + if make install is given. Affected files: all Makefile.am's. + +Thu Oct 25 22:44:06 EDT 2001 Pekka Riikonen + + * Assure that silc_client_notify_by_server_resolve does not + resolve the client information multiple times. If it cannot + be found by the first it cannot be found at all. Affected + file lib/silcclient/client_notify.c. + + * Fixed WHOWAS command reply calling. Affected file + lib/silcclient/command_reply.c. + + * Removed all references to silc_idlist_get_client from the + Irssi SILC client since that call is internal call used by + the library. The Irssi SILC client will use now client + retrieval functions found in silcapi.h. + + * Fixed a bug in resolving nickname info before sending + private message. It used freed memory. Affected file + irssi/src/silc/core/silc-servers.c. + +Thu Oct 25 19:04:49 EDT 2001 Pekka Riikonen + + * Assure my_channels statistics cannot go negative in server. + Affected files silcd/server.c, silcd/server_util.c. + +Wed Oct 24 19:53:05 EDT 2001 Pekka Riikonen + + * Upgraded dotconf 1.0.2 to 1.0.6 in lib/dotconf. + +Tue Oct 23 13:51:19 EEST 2001 Pekka Riikonen + + * Win32 Toolkit changes. Affected files + win32/silc.dsw, win32/libsilc/libsilc.def, + win32/libsilcclient/libsilc.def, + lib/silcutil/silcutil.c, and + lib/sftp/sftp_fs_memory.c. + +Mon Oct 22 16:35:05 EDT 2001 Pekka Riikonen + + * Added silc_net_localip to return local host's IP address. + Affected file lib/silcutil/silcnet.[ch]. + + * If key exchange or rekey protocol is active for a connection + parse all packets syncronously since there might be packets + in packet queue that we are not able to process without first + processing packets before them. Affected file silcd/server, + lib/silcclient/client.c. + + * SilcPacketParserCallback now returns TRUE or FALSE to indicate + whether library should continue processing the packet. + Affected file lib/silccore/silcpacket.h. + + * Added SilcSFTPMonitor callback, SilcSFTPMonitors and + SilcSFTPMonitorData to SFTP server to monitor various + SFTP client requests. Affected file lib/silcsftp/silcsftp.h, + lib/silcsftp/sftp_server.c. + + * Added silc_file_size to return file size. Affected file + lib/silcutil/silcutil.[ch]. + + * Implemented the file transfer support for the client library. + Added preliminary support for simple client to client one-file + transmission. Affected file lib/silcclient/client_ftp.c, + lib/silccilent/client.[ch]. + + * Added new local command FILE to the Irssi SILC Client. + It is used to perform the file transfer. It has subcommands + SEND, RECEIVE, SHOW and CLOSE. Affected files + irssi/src/silc/core/client_ops.c, + irssi/src/silc/core/silc-server.[ch]. + +Mon Oct 22 12:50:08 EDT 2001 Pekka Riikonen + + * Relay the SILC_PACKET_FTP in the server. Affected files + silcd/server.c and silcd/packet_receive.c. + +Sun Oct 21 20:21:02 EDT 2001 Pekka Riikonen + + * Renamed silc_file_read and silc_file_write to functions + silc_file_readfile and silc_file_writefile. Added function + silc_file_open and silc_file_close. Affected files + lib/silcutil/silcutil.[ch]. + +Thu Oct 18 20:58:13 EDT 2001 Pekka Riikonen + + * Resolve the client info when received private message or + channel message for a client which nickname we don't know. + Affected files lib/silcclient/client_prvmsg.c and + lib/silcclient/client_channel.c. + + * Do not crash in /KEY if client is not connected. Affected + file irssi/src/silc/core/silc-channels.c. + + * Added SilcClientStatus field to the SilcClientEntry in the + lib/silcclient/idlist.h. + + Added SILC_CLIENT_STATUS_RESOLVING to mark that the entry + is incomplete and is being resolved, it won't be resolved + twice. + + Make sure also that USERS command reply does not resolve + twice information. Affected file is + lib/silcclient/command_reply.c. + + Make sure that silc_client_get_clients_by_list does not + resolve twice same information. + + * Check for valid client->id in the silc_server_free_client_data. + Affected file silcd/server.c. + + * Fixed /GETKEY nick@server not to crash if the server entry + is not found. Affected file lib/silcclient/command.c. + + * Fixed the silc_server_check_cmode_rights to check the + requested modes correctly. Affected file silcd/command.c. + +Thu Oct 18 12:10:22 CEST 2001 Pekka Riikonen + + * Better checks for non-printable chars in nick added. + Affected file silcd/command.c. + +Thu Oct 18 09:18:58 EDT 2001 Pekka Riikonen + + * Call the silc_server_udpate_servers_by_server in the + primary router that comes back online in the backup resuming + protocol. Otherwise it routes packets wrong. Affected file + silcd/server_util.[ch], silcd/server_backup.c. + +Wed Oct 17 16:51:18 EDT 2001 Pekka Riikonen + + * Added SILC_STR_UI8_[N]STRING[_ALLOC] formats to the + lib/silcutil/silcbuffmt.[ch]. + + * Redefined the SILC packet header to include the padding + length. Affected file lib/silccore/silcpacket.[ch]. + + * Added SILC_PACKET_PADLEN_MAX macro to return the padding + length for maximum padding up to 128 bytes). Affected + file lib/silccore/silcpacket.h. + + * Removed all backwards support for old 0.5.x MAC thingies. + The SILC packet header change makes it impossible to be + backwards compatible. + + * Send the ENDING packet with timeout in the backup resuming + protocol. This is to assure that all routers has connected + to the primary router. Affected file silcd/server_backup.c. + + * Changed the RNG to take the first IV from random data. It + used to take it from zero actually. Changed the RNG also + to use /dev/urandom during session. /dev/random is used + in initialization. Affected file lib/silccrypt/silcrng.[ch]. + +Tue Oct 16 20:45:49 EDT 2001 Pekka Riikonen + + * Changed the SILC packet header to have the first two bytes + (the packet length) encrypted. Affected files aroung the + code tree, lib/silccore/silcpacket.[ch]. Removed the + SilcPacketCheckDecrypt callback. It is not needed anymore + since the silc_packet_receive_process will determine now + whether the packet is normal or special. + + * Implemented the unidirectional MAC keys. Affected files + lib/silcske/silcske.c, silcd/protocol.c and + lib/silcclient/protocol.c. + + * Implemented the packet sequence number to the MAC computation. + Affected files lib/silccore/silcpacket.c, silcd/protocol.c, + silcd/packet_send.c, silcd/server.c, lib/silcclient/client.c, + lib/silcclient/protocol.c. + +Mon Oct 15 17:42:55 EDT 2001 Pekka Riikonen + + * Allow backup router to announce servers. All servers + announced by backup router are added to the global list + automatically. Update hte server's socket to our primary + router also when backup router announces a server. + Affected file silcd/packet_receive.c. + + * Do not update the client->router in the function + silc_server_udpate_clients_by_server if the client is on + global list. We might fail to find any specific server + for locally connected clients and local cell clients. They + should still use the `from' and not `to' as client->router. + This fixes backup router resuming protocol. Affected file + silcd/server_util.c. + + * Decrease channel statistics count only if the channel + deletion worked. Affected files are silcd/server.c and + silcd/server_util.c. + + * Added silc_server_update_servers_by_server to update origin + of all server entries. Used during backup router protocol. + Affected files silcd/server_util.[ch], silcd/server.c. and + silcd/backup_router.c. + + * ROBODoc documented the lib/silccrypt/silchmac.h. Added new + function silc_hmac_init, silc_hmac_update, silc_hmac_final, + silc_hmac_get_hash and silc_hmac_get_name. Affected file + lib/silccrypt/silchmac.c. + +Sun Oct 14 18:28:22 EDT 2001 Pekka Riikonen + + * Assure that router cannot reroute the same channel message + to the sender. Affected file silcd/packet_receive.c. + +Sat Oct 13 12:46:18 EDT 2001 Pekka Riikonen + + * Made better checks that the channel message is not sent + to the router it came from. Affected file is + silcd/packet_send.c. Fixed memory leak too. + + * Announce informations for incoming router connection, but + only after checking if it is replaced by backup router. + Affected file silcd/packet_receive.c. + +Fri Oct 12 18:37:24 EDT 2001 Pekka Riikonen + + * Fixed the backup resuming protocol to work in multiple + router environment. Affected file silcd/server_backup.c. + + * Route packet only to one router in the function + silc_server_packet_send_to_channel. Affected file is + silcd/packet_send.c. + + * Fixed silc_server_send_notify_dest to set the broadcast + flag. Fixed the silc_server_send_notify_topic to actually + send the TOPIC_CHANGE notify and not SERVER_SIGNOFF notify. + Affected file silcd/packet_send.c. + + * Changed the SFTP Filesystem interface. Changed the + SilcSFTPFilesystemStruct to SilcSFTPFilesystemOps to include + the filesystem operation function. The SilcSFTPFilesystem + is now a context that is allocated by all filesystem allocation + functions and it already includes the operations structure + and filesystem specific context. It is given as argument + now to the silc_sftp_server_start. This made the interface + a bit cleaner. Affected file lib/silcsftp/silcsftp[_fs].h, + lib/silcsftp/sftp_fs_memory.c and sftp_server.c. + +Thu Oct 11 22:19:26 EDT 2001 Pekka Riikonen + + * Changed the backup router adding and getting interfaces + in the server. The router that will be replaced by the + specified backup router is now sent as argument. Affected + files silcd/serverconfig.[ch], silcd/backup_router.[ch], and + silcd/server.c. + + * Added silc_net_addr2bin_ne to return the binary form of + the IP address in network byte order. Affected files + lib/silcutil/[unix/win32].silc[unix/win32]net.[ch]. + +Thu Oct 11 12:14:19 EDT 2001 Pekka Riikonen + + * Check for existing server ID in silc_server_new_server + and in silc_server_connect_to_router_final and remove the + old entry if it exists. Affected file silcd/packet_receive.c, + silcd/server.c. + + * Send the channel message always to only one router, either + in upstream or downstream. Affected file is + silcd/packet_send.c. + +Tue Oct 9 17:45:43 EDT 2001 Pekka Riikonen + + * Wrote the definition of the backup resuming protocol to the + protocol specification. + + * Removed one redundant channel key generation from normal + server during joining procedure. Removed one redundant + channel key sending from server to router during joining + procedure. Affected file silcd/command.c. + + * Made minor bugfixes to the backup router resuming protocol. + Affected file silcd/server_backup.c, server.c. + +Mon Oct 8 16:47:42 EDT 2001 Pekka Riikonen + + * Added --disable-asm configuration option. Affected files + configure.in.pre, lib/silcmath/mpi/configure.in. A patch + by salo. + + * Implemented the backup resuming protocol that is used to + resume the primary router position in the cell after the + primary router comes back online. Affected files + silcd/server_backup.[ch], silcd/server, silcd/packet_receive.c, + and silcd/server_util.[ch]. + +Sun Oct 7 12:29:25 EDT 2001 Pekka Riikonen + + * Sleep two (2) seconds after sending QUIT command to server. + Affected file lib/silcclient/command.c. + + * Assure that if outgoing data buffer is pending do not force + send any data. Affected file silcd/packet_send.c. + + * Assure that if outgoing data buffer is pending do not force + send any data. Affected file lib/silcclient/client.c. + + * Implemented the backup router support when the primary router + goes down. The servers and routers can now use the backup + router as new primary router without loosing connectivity. + +Sat Oct 6 21:18:54 EDT 2001 Pekka Riikonen + + * Added new SILC_IDLIST_STATUS_DISABLED flag for entries + in the server to indicate disabled entry. All data read + from the connection will be ignored and no data is sent + for entry that is disabled. Affected files are + silcd/idlist.h, silcd/server.c. + +Fri Oct 5 00:03:29 EDT 2001 Pekka Riikonen + + * Created SFTP client and server test programs in the + lib/silcsftp/tests directory. + +Wed Oct 3 23:31:42 EDT 2001 Pekka Riikonen + + * Implemented memory filesystem (virtual filesystem) for + SFTP server. Affected file lib/silcsftp/silcsftp_fs.h, + sftp_fs_memory.c. + +Sun Sep 30 22:10:57 EEST 2001 Pekka Riikonen + + * Implemented the SFTP (SSH File Transfer Protocol) to the + lib/silcsftp. It includes SFTP client and SFTP server + implementations. + +Sun Sep 30 10:35:44 EEST 2001 Pekka Riikonen + + * Moved lib/silccore/silcprotocol.[ch] to the + lib/silcutil library. + + * Added silc_buffer_format_vp and silc_buffer_unformat_vp to + take variable argument list pointer as argument. Affected + file lib/silcutil/silcbuffmt.[ch]. + + * Added silc_buffer_set function that is used to set data + to a SilcBuffer that is not allocated at all (SilcBufferStruct). + Affected file lib/silcutil/silcbuffer.h. + + * Changed various routines in the core library to use the new + silc_buffer_set instead of allocating new buffer only for + temporary purposes. + + * Added 64-bit value formatting and unformatting support to the + silc_buffer_[un]format routines. Affected file is + lib/silcutil/silcbuffmt.[ch]. + + Added also 64-bit macros: SILC_GET64_MSB and SILC_PUT64_MSB, + to includes/bitmove.h. + +Fri Sep 28 21:30:10 EEST 2001 Pekka Riikonen + + * Fixed channel user mode saving in client library. Affected + file lib/silcclient/command[_reply].c. + +Thu Sep 27 22:52:30 EEST 2001 Pekka Riikonen + + * Defined the file transfer to the SILC Protocol. Added + new packet type SILC_PACKET_FTP and defined File Transfer + Payload. The mandatory file transfer protocol is SFTP + (SSH File Transfer Protocol). Affected file in addition + of the internet draft is lib/silccore/silcpacket.h. + + * Deprecated the SILC_PACKET_CELL_ROUTERS and defined new + packet SILC_PACKET_RESUME_ROUTER instead. The new packet + is used as part of backup router protocol when the primary + router of the cell is back online and wishes to resume + the position as primary router. + + * Redefined the MAC generation keys in the protocol. The + same key is not used anymore in both direction. Both + direction will now use different keys for sending and + receiving. This fixes a potential security flaw. This + change causes incompatibilities in the protocol. + + * Redefined also the MAC computation from the packet. + An packet sequence number is now added to the MAC + computation. This prevents possible replay attacks against + the protocol. This change too causes incompatibilities + in the protocol. + + Added `sequence' field to the SilcPacketContext to hold + the current sequence number for the packet. + +Wed Sep 26 20:15:22 EEST 2001 Pekka Riikonen + + * Added `created' field to the SilcIDListData in the file + silcd/idlist.h to indicate the time when the entry was + created. + + * Added `created' field to the SilcChannelEntry too. Affected + file silcd/idlist.h. + + * Added `creation_time' aguments to all the announcement functions + in the server. If it is provided then only the entries that + was created after the provided time frame are actually + announced. Affected file silcd/server.[ch]. + + * The protocol says that the Channel ID's IP address must be + based on the router's IP address. Added check for this in + the silc_server_new_channel when processing incoming New Channel + Payload. Affected file silcd/packet_receive.c. + + * Print out the correct version with --version in SILC client. + Affected file irssi/src/silc/core/silc-core.c. + +Mon Sep 24 17:19:00 EEST 2001 Pekka Riikonen + + * Fixed WHOWAS command to check for completnes of the client + entry always, not just when the command is coming from client. + Affected file silcd/command.c. + + * Added new function silc_server_packet_queue_purge to purge the + outgoing data queue to the network. After the function returns + it is guaranteed that the outgoing packet queue is empty. + Affected file silcd/packet_send.[ch]. + + * Purge the outgoing packet queue in the rekey protocol's final + callback to assure that all rekey packets go to the network + before quitting the protocol. Affected file silcd/server.c. + + * Added silc_client_packet_queue_parse as similar function as + in server to the client library. The affected file is + lib/silcclient/client.c. + +Sun Sep 23 15:15:53 EEST 2001 Pekka Riikonen + + * Splitted silcd/server.c and created silcd/server_util.[ch] + for utility functions. + + * Added new socket flag SILC_SF_DISABLED to indicate that the + connection is open but nothing can be sent to or received from + the connection. Affected file lib/silcutil/silsockconn.[ch]. + The checking for disabled socket is checked in the low level + silc_socket_write and silc_socket_read functions. + +Thu Sep 20 23:11:28 EEST 2001 Pekka Riikonen + + * Allow only nicknames and channel names that fits into the + 7-bit unsigned char ASCII set. Affected file silcd/command.c. + +Thu Sep 20 18:04:12 EEST 2001 Pekka Riikonen + + * When processing JOIN command reply in server check that if + the channel exists in our global list we'll move it the local + list. Affected file silcd/command_reply.c. + + * Fixed the check whether client is joined on the channel already + in JOIN command. Affected file lib/silcclient/command.c. + + * Fixed the JOIN command reply to check whether the channel + already exists. Affected file lib/silcclient/command_reply.c. + +Wed Sep 19 22:58:32 EEST 2001 Pekka Riikonen + + * Added silc_ske_status_string to map the SKE error numbers + to readable strings. The affected files are + lib/silcske/silcske[_status].[ch]. + +Tue Sep 18 22:50:41 EEST 2001 Pekka Riikonen + + * Do not show the private channels on the WHOIS channel list + as it is not allowed by the protocol. The affected file is + silcd/server.c. + +Sun Sep 16 12:32:58 EEST 2001 Pekka Riikonen + + * Assure that the packet length digged from the actual packet + is something sensible in the silc_packet_decrypt_rest_special + in lib/silccrypt/silcpacket.c. + + * Free and NULL the allocated pointer in silc_hmac_alloc if + the HMAC allocation fails. The affected file is + lib/silccrypt/silchmac.c. + + * Print the selected security properties to the log files in + the server. Affected file silcd/protocol.c. + + * Add SKE's reference counter even if calling the completion + callback manually. Otherwise it goes negative, although it + does not cause any problems. The affected file is + lib/silcske/silcske.c. + + * Remove the client entry with short timeout after giving the + KILL command. Affected file lib/silcclient/command.c. + + * Fixed to send error reply in WHOIS and IDENTIFY commands in + case all found clients are already disconnected (WHOWAS would + found them) in the server. Affected file silcd/command.c. + + * Update the last_receive (time of last data received) to be + updated only when received private or channel message so that + the idle time showed in WHOIS makes more sense. + + * Added boolean field `valid' in to the SilcClientEntry in the + client library to indicate whether the entry is valid or not. + This fixes the nickname change bug on channel when changing + the nickname to be same than the old (like nick to Nick) the + nickname formatter doesn't set the new nick anymore to Nick@host. + Affected file lib/silcclient/idlist.[ch]. + + * Now actually fixed the nickname changing on disconnection. + Added new function silc_change_nick to the Irssi SILC Client. + Affected file irssi/src/silc/core/client_ops.c, + irssi/src/silc/core/silc-nicklist.[ch]. + +Sat Sep 15 13:29:17 EEST 2001 Pekka Riikonen + + * Check that the public key exists in the GETKEY command before + trying to encode it. Affected file silcd/command.c. + + * Print some notifications on received public keys with GETKEY + command in the Irssi SILC Client. Affected files are + irssi/src/fe-common/silc/module-formats.[ch], + irssi/src/silc/core/client_ops.c. + + * Use IDENTIFY command to resolve the server information in the + GETKEY command instead of INFO command. Affected file + lib/silcclient/command.c. + + * All command reply functions in the client library now calls + the pending command reply callbacks even if an error has + occurred. The server has done this a long time and now it was + time to move the client library to this as well. Now all + errors can be delivered back to the pending command reply + callbacks if necessary. Affected files are + lib/silcclient/command[_reply].[ch]. + + * Change the nickname on disconnection back to the username + because in reconnect the server will enforce it to it anyway. + Affected file irssi/src/silc/core/silc-servers.c. + + * Fixed a config file parsing bug in the Irssi SILC client. + Affected file irssi/src/silc/core/clientconfig.c. + +Thu Sep 13 23:11:18 EEST 2001 Pekka Riikonen + + * When printing the channel mode on JOIN, verify that the + channel key and channel's HMAC are valid. Affected file + irssi/src/silc/core/client_ops.c. + +Thu Sep 13 20:24:52 EEST 2001 Pekka Riikonen + + * Added defines SILC_DEFAULT_CIPHER, SILC_DEFAULT_HMAC, + SILC_DEFAULT_HASH and SILC_DEFAULT_PKCS in the file + lib/silccrypt/[silccipher.h|silchmac.h|silchash.h|silcpkcs.h]. + + * Removed channel key rekey task deleting from the function + silc_server_save_channel_key. Affected file silcd/server.c. + Added explicit timeout task context instead that is used to + delete the task if we are registering a new task before the + new task has elapsed. + + * When channel key rekey occurs the client library now saves + the old channel key for a short period of time (10 seconds) and + is able to use it in case some is still sending channel + messages encrypted with the old key after the rekey. Affected + file lib/silcclient/[idlist.h|client_channel.c]. + +Sun Sep 9 15:49:16 EEST 2001 Pekka Riikonen + + * Added check to the silc_server_new_id_real to not accept + new ID if it is the sender's own ID. Affected file is + silcd/packet_receive.c. + + * Assure that we do not announce ourself or the one we've + sending our announcements when we're router and are announcing + servers to our primary router. Affected file silcd/server.c. + + * Fixed silc_server_command_identify_check_client to assemble + correct WHOIS packet. It send corrupted WHOIS packet and + caused problem with router to router connections. Affected + file silcd/command.c. + + Fixed also silc_server_command_whois_check the same way + as for the IDENTIFY command. + + * Added new SilcIDListStatus to the server in the SilcIDListData + structure. The status now includes the current status of + the entry (like registered, resolved etc.). Affected file + silcd/idlist.[ch]. Defined a bunch of different status types + as well. This replaced the old boolean registered field as well. + + Added resolve_cmd_ident field to the SilcClientEntry structure + too so that if the entry is for example being resolved so + another command may attach to the same pending command reply + without requiring to resolve the same entry again. This concept + should optimize the WHOIS and the IDENTIFY resolving under + heavy load by taking away unnecessary resolving for entries + that are being resolved already. + + Added support for adding multiple pending commands for one + command idenfier. Affected file silcd/command[_reply].[ch]. + + * Fixed WHOIS and IDENTIFY save to remove the cache entry + before deleting the data. Otherwise the hash table will have + freed data in comparison functions. Affected file is + silcd/command_reply.c. + + * Fixed silc_idlist_replace_client_id to add the new entry to + the cache with NULL nickname. Otherwise there will be invalid + memory as the nickname after the nickname is freed. Affected + file silcd/packet_receive.c. + + * Fixed the silc_idlist_get_clients_by_hash. The entries was + saved into wrong slots because the previous number of entries + was not taken into account. Affected file silcd/idlist.c. + Fixed same thing in silc_idlist_get_clients_by_nickname too. + + * If we are router and we receive JOIN notify to a channel that + does not have any users then notified client is marked as the + channel founder, as it is it. The affected file is + silcd/packet_receive.c + + * Added to the extended hash table API's table_del_*ext functions + the destructor as argument too, so that the caller can decide + which destructor to use or whether to use destructor at all. + Affected file lib/silcutil/silchashtable.[ch]. + + * Fixed ID Cache purging. It actually deleted the entries from + the hash table after the data was freed. The hash table ended + up comparing freed memory. The affected file is + lib/silccore/silcidcache.c. + +Sat Sep 8 10:22:10 EEST 2001 Pekka Riikonen + + * Fixed Irssi SILC client's KILL command's HELP syntax. + + * The USERS command now resolves the detailed user information + if the userinfo field is missing. Affected file is + lib/silcclient/command_reply.c. + + * Do not print error in silc_file_read if the read file does + not exist. Just silently return NULL. Affected file is + lib/silcutil/silcutil.c. + + * Fixed the silc_log_output to not wine about NULL filename + and to not create some bogus " " filename. Affected file is + lib/silcutil/silclog.c. + +Fri Sep 7 22:16:38 EEST 2001 Pekka Riikonen + + * Fixed various printing bugs on the user interface in the + Irssi SILC Client. Minor changes that were forgotten from + the release. + +Fri Sep 7 17:28:37 EEST 2001 Pekka Riikonen + + * Fixed the configure.in.pre and the compilation and distribution + environment to support the new autoconf 2.52. That version is + now required to compile the CVS trunk. + +Thu Sep 6 12:47:37 EEST 2001 Pekka Riikonen + + * Renamed function silc_parse_nickname to silc_parse_userfqdn + to generally parse user@fqdn format strings. Affected file + lib/silcutil/silcutil.c. + + * Added nickname_format and nickname_force_format fields to the + SilcClientParams structure. The first one defines the format + for the nicknames that the library will enforce if the receives + multiple same nicknames. The second one is boolean value and + can be used to force the library to always enforce the format + to the nicknames regardles whether there are multiple nicknames + or not. This configurable formatting was employed to flexibly + support accessing multiple nicknames from the user interface. + The userinterface can now set the nicknames to what ever format + they prefer. Affected file lib/silcclient/silcapi.h. + + Added function silc_client_nickname_format to the file + lib/silcclient/idlist.c. It performs the nickname formatting. + + Added new field `hostname´ to the SilcClientEntry context. + It holds the hostname of the client. Affected file is + lib/silcclient/idlist.h. + + * Irssi SILC Client sets the nicknames in nick@hostn format. + Fe. priikone@otaku, priikone@otaku2 etc. Affected file + irssi/src/silc/core/silc-core.c. + + The WHOIS printing now also shows both the real nickname and + the formatted nickname so that user knows how to access the + user if there are multiple same nicknames cached. Affected + file irssi/src/silc/core/client_ops.c. Changed the WHOIS + printing formatting too to take the hostname now as a separate + argument. The Affected file is + irssi/src/fe-common/silc/modules-formats.[ch]. + + * Changed the silc_client_get_clients_local to accept the formatted + nickname as argument. It accepts the real nickname too but the + formatted nickname can be used to find the true entry from + multiple entries. Affected file lib/silcclient/silcapi.h and + lib/silcclient/idlist.c. + + * Added nickname_format_parse field to the SilcClientParams. + It is a callback function provided by the application to parse + the nickname out of the formatted nickname string. The library + calls it to get the nickname from the formatted string. Since + the application generally knows better the format of the nickname + string it parses it instead of the library, even though library + encodes the formatted string. If the callback function is not + provided then the library will use the string as is. The + affected file is lib/silcclient/silcapi.h. + + * All the nickname strings passed to the client library in + commands are now expected to be formatted nickname strings. + If the command does not support the formatted nickname string + it will assume that the sent string is the actual nickname. + Affected file lib/silcclient/command.c. + +Tue Sep 4 22:31:28 EEST 2001 Pekka Riikonen + + * Added public key authentication support to OPER and SILCOPER + commands in the client library. Affected file is + lib/silcclient/command.c. + +Tue Sep 4 12:39:17 EEST 2001 Pekka Riikonen + + * Changed the get_auth_methdod client operation to be asynchronous. + It can be async if the application resolves the authentication + method from the server during the negotiation. Added new + SilcGetAuthMeth completion callback that the application will + call after resolving the authentication method. + + Added function silc_client_request_authentication_method that + the application can use to resolve the authentication method + from the server. Added also SilcConnectionAuthRequest callback + that the library will call after the server has replied. The + application can call this function if it does not know the + current authentication method. + + Affected files are lib/silcclient/client.c and + lib/silcclient/silcapi.h. + + * The Irssi SILC client now automatically resolves the authentication + method incase any configuration information is not present (and + currently there never is). The affected file is + irssi/src/silc/core/client_ops.c. + + * Fixed public key authentication from the client library. + Affected file lib/silcclient/protocol.c. Changed also the + protocol specification about the public key authentication in + the connection authentication protocol. The actual data to be + signed is now computed with a hash function before signing. + + * Fixed the public key authentication from the server as well. + Affected file silcd/protocol.c. + + * Removed the mlock()'s from the memory allocation routines. + Affected file lib/silcutil/silcmemory.c. The ./configure does + not check anymore for the mlock(). Affected file is + configure.in.pre. + + * Fixed USERS command in server to allow the execution of the + command for private and secret channels if the client sending + the command is on the channel. Affected file silcd/command.c. + + * Fixed silc_client_get_clients_local to return the clients + count correctly. It could return wrong value. Affected file + lib/silcclient/idlist.c. + +Mon Sep 3 20:09:59 EEST 2001 Pekka Riikonen + + * Fixed the lib/silcmath/mpi/mpi.h to always use 32-bit data + types. The assembler optimizations seemed not to like 64-bit + data types. The assmebler optimizations thus are now enabled + also for BSD systems as opposed to only enable them for Linux. + + * Do not check for threads at all on BSD systems. Affected + file configure.in.pre. + + * Removed -n and -h options from the Irssi SILC Client since + they are not used in silc. + + * Fixed the prime generation to assure that the first digit + of the generated random number is not zero since our conversion + routines does not like number strings that starts with zero + digit. If zero digit is seen the random number is regenerated. + This caused some corrupted RSA keys when the zero first digit + was met. Affected file lib/silcmath/silcprimegen.c. + +Sun Sep 2 17:17:24 EEST 2001 Pekka Riikonen + + * Fixed WIN32 configuration in the ./configure script. + Fixed to include xti.h on environments that has it. + Patches by Carsten Ilchmann and andrew. + +Sat Sep 1 00:29:33 EEST 2001 Pekka Riikonen + + * Changed the silc_id_create_client_id to be collision + resistant. It is now assured that there cannot be created + two same client ID's. I suspect that some weird bugs in + the server were actually caused by duplicate Client IDs. + Affected file silcd/serverid.[ch]. A router receiving + new ID now also assures and informs the sending server + if the ID caused collision. + + * Changed the silc_id_create_channel_id to also assure that + there are no collisions. + +Wed Aug 29 17:55:01 EEST 2001 Pekka Riikonen + + * Statement about ignoring the Mutual Authentication flag when + performing rekey with PFS was a bit misleading. It is ignored + if it was set in the initial negotiation, it cannot be even + set in the rekey. Fixed in the ke-auth draft. Started the + new versions of the protocol drafts in the doc/. + +Sun Aug 26 14:59:15 EEST 2001 Pekka Riikonen + + * Fixed a bug in silc_client_command_identify_save when saving + new channel information. The channel name was no duplicated + and caused crash on exit. Affected file is + lib/silcclient/command_reply.c. + +Fri Aug 17 23:07:45 EEST 2001 Pekka Riikonen + + * Fixed the getkey command handling in the server. Send just + empty OK reply to the sender if the key could not be fetched + (but everything else was ok, like the key just was not available). + Changed the public key parameter to optional in the protocol + specs so that empty OK reply can be sent. Affected file + silcd/command.c. + + Added a message to Irssi SILC client to tell to user if the + server did not return a public key. + +Tue Aug 14 07:29:27 CEST 2001 Pekka Riikonen + + * Fixed a channel key regeneration bug. It registered new + timeout tasks exponentially until all system resources were + used. Affected file silcd/server.c. + +Sun Aug 12 20:48:14 EEST 2001 Pekka Riikonen + + * Added the SILC Document generator to the scripts/silcdoc. + It can be used to generate the Toolkit Reference Manual out + of the source tree. Internally it will also use the RoboDoc + generator now imported in util/robodoc. + +Sun Aug 12 12:28:17 EEST 2001 Pekka Riikonen + + * Added couple of return's in rekey protocol if error orccurred + during the protocol. The execution must be terminated. + Affected file silcd/protocol.c. Also, terminate the protocol + always with timeout. + +Sat Aug 11 12:36:02 EEST 2001 Pekka Riikonen + + * The client's Client ID was created initally from the wrong + nickname (it could have been in format nick@host) in the + silc_server_new_client. Affected file silcd/packet_receive.c + +Sat Aug 11 00:29:57 EEST 2001 Pekka Riikonen + + * Added some SILC_LOG_ERROR's to various error conditions + if client could not be added to ID cache. Affected files + silcd/packet_receive.c and silcd/server.c. + + * When client's sock->user_data is freed, NULL also the + client->router and client->connection pointers. Added check + for these pointers being NULL to various places around the + code. Affected file silcd/server.c. + + * Added client->data.registered == TRUE checks to various + places around the code to assure that unregistered client's + are not handled when it is not allowed. Affected file + silcd/server.c. + + * Added `bool registered' fields to all + silc_idlist_[server|client]_get_* routines to indicate whether + the fetched client needs to be registered or not. Affected + file silcd/idlist.[ch]. + + * Add your own entry as registered to the ID cache in the + server. Affected file server.c. + + * Fixed a bug in silc_server_new_server. The SilcServer was + set as the new server's context instead of SilcServerEntry. + This naturally caused some weird bugs. + +Thu Aug 9 18:28:37 EEST 2001 Pekka Riikonen + + * Do not delete the channel rekey task when adding it + for in silc_server_create_channel_key. + + * Changed the silc_server_create_channel_key to return + TRUE or FALSE to indicate the success of the channel key + creation. + +Thu Jul 26 11:32:31 EEST 2001 Pekka Riikonen + + * Fixed MSVC++ project files and added missing files to + Makefiles under win32/. + +Wed Jul 25 18:43:54 EEST 2001 Pekka Riikonen + + * Do not add TCP_NODELAY flag if the operating system + does not have it defined. Affected files are + lib/silcutil/[unix/win32]/silc[unix/win32]net.c. + + * Fixed buffer overflow from Irssi SILC Client. Affected + file irssi/src/fe-common/core/themes.c. + + * Fixed double free in client library in the file + lib/silcclient/client.c when disconnecting from server. + + * Applied double free patch from cras to Irssi SILC client. + Affected files irssi/src/core/[modules/expandos].c + + * Fixed the disconnection handling to Irssi SILC Client. + The application must call silc_client_close_connection + in ops->connect client operation in case of failure of + the connection. Affected file is + irssi/src/silc/core/client_ops.c. + + * Do not set sock->protocol to NULL in the function + silc_client_close_connection after executing the protocol's + final callback since the sock might not be valid anymore. + Affected file lib/silcclient/client.c. + +Wed Jul 25 16:04:35 EEST 2001 Pekka Riikonen + + * Do not enable SILC_THREADS if the linking with libpthread + did not happen. Affected file configure.in.pre. + + * Added notion to protocol specification that server must + verify the sent authentication payload with CMODE when + setting the channel founder key. Implemented it to the + server. Affected file silcd/command.c. + +Mon Jul 23 18:31:43 EEST 2001 Pekka Riikonen + + * Added _EXTRA_DIST SILC distribution variable to the + distributions file. It is used to conditionally add extra + files or directories to the specific distribution. Affected + files ./prepare, Makefile.am.pre and distributions. + + Removed the `_' from the start of the distribution names. + It is redundant. + + * Added README.WIN32 for instructions to compile the Toolkit + under WIN32. + +Mon Jul 23 10:12:37 EEST 2001 Pekka Riikonen + + * Fixed a double free in disconnection in the server. Affected + file is silcd/server.c. + + * Fixed the lib/silcske/groups.c to work now also with GMP + MP library. The string conversion did not work when using + specific base and the base is indicated in the string as well. + + * Created win32/ directory which now includes MSVC++ specific + stuff so that toolkit (DLLs) may be compiled with MSVC++. + It will appear only in the toolkit distribution + +Sun Jul 22 19:40:30 EEST 2001 Pekka Riikonen + + * Changed the key material distribution function in case when + the hash output is too short. The data is now concatenated + a bit differently than it used to. Made the change to the + SKE protocol specification. + + * Added better GMP detection to configure.in.pre. A patch + by salo. + +Fri Jul 20 13:16:00 EEST 2001 Pekka Riikonen + + * Fixed a minor bug in SKE that might cause some problem on + some platforms. Affected file lib/silcske/silcske.c. + + * Added the cookie checking for initiator in the SKE. It checks + that the responder returns the sent cookie unmodified. The + affected file is lib/silcske/silcske.c. Added new SKE + error type INVALID_COOKIE that can be sent during the + negotiation. Fixed some memory leaks as well. + + * Added the "invalid cookie" error message to Irssi SILC client's + message formats. + +Thu Jul 19 21:44:31 EEST 2001 Pekka Riikonen + + * Added `task_max' field to the SilcClientParams to indicate + the maximum tasks the scheduler can handle. If set to zero, + default values are used. Affected file lib/silcclient/silcapi.h. + + * Fixed memory leaks in silc_client_close_connection. Affected + file lib/silcclient/client.c. + + * Added silc_client_del_client_entry to client library to free + all memory of given client entry. Affected file is + lib/silcclient/idlist.[ch]. + + * Added new functions silc_client_del_channel and + silc_client_del_server to delete channel and server entries. + Affected file lib/silcclient/[silcapi.h/idlist.c]. + + * Removed silc_client_del_client_by_id from silcapi.h. + + * Fixed the INFO command to return the server's own info + correctly when querying by Server ID. Affected file is + silcd/command.c. + +Thu Jul 19 14:47:30 EEST 2001 Pekka Riikonen + + * Removed the non-blocking settings in WIN32 code in the + silc_sock_[read/write] and added SleepEx instead. Affected + file lib/silcutil/win32/silcwin32sockconn.c. The availability + of input data is now checked with FIONREAD and ioctlsocket. + +Wed Jul 18 18:34:01 EEST 2001 Pekka Riikonen + + * Call silc_schedule_task_del_by_context in the + silc_protocol_cancel instead of silc_schedule_task_del_by_callback. + Affected file lib/silccore/silcprotocol.c. + + * Call silc_protocol_cancel for active protocols in the + silc_server_close_connection if the funtion + silc_server_free_sock_user_data has not been called. + Affected file silcd/server.c. + + * Generic tasks cannot be deleted using the del_by_fd + task deleting function since generic tasks does not match + any specific fd. Affected file lib/silcutil/silcschedule.[ch]. + + * Added a notion to SILCOPER help file that the SILCOPER works + only on router server, not on normal server. + +Wed Jul 18 09:40:04 EEST 2001 Pekka Riikonen + + * Added for WIN32 support for the new scheduler as well. + Affected file lib/silcutil/win32/silcwin32schedule.c. + + * Fixed the SHA1 implementation to work on various platforms. + +Tue Jul 17 23:04:10 EEST 2001 Pekka Riikonen + + * Rewrote the SILC Scheduler entirely. Removed the old SILC Task + API. It is part of the scheduler now. Everything else is + as previously but some functions has changed their names. + Checkout the lib/silcutil/silcschedule.h for the interface. + Updated all applications to use the new interface. Affected + files are lib/silcutil/silcschedule.[ch]. + +Tue Jul 17 16:53:30 EEST 2001 Pekka Riikonen + + * Found a bug in the SKE implementation. The HASH value, + specified by the protocol, was not computed correctly. The + public key of the responder was not added to the computation + even though it is mandatory. Affected file lib/silcske/silcske.c. + This unfortunately causes incompatibilities with older + clients and servers. + + * Added WIN32 specific network init and uninit functions: + silc_net_win32_init and silc_net_win32_uninit to init and uninit + the Winsock2. Affected file lib/silcutil/silcnet.h and + lib/silcutil/win32/silcwin32net.c. + + * Set the socket always to nonblocking mode on WIN32 after + reading data or writing data. Affected file is + lib/silcutil/win32/silcwin32sockconn.c. + +Mon Jul 16 22:55:26 EEST 2001 Pekka Riikonen + + * Fixed various compilation problems under WIN32. Affected + files lib/silcutil/win32/silcwin32thread.c and + lib/silcutil/win32/silcwin32schedule.c. + + * Removed all _internal.h #includes from public header + files. Internal headers must never be included from + public headers. + + Removed also the lib/silcske/payload_internal.h file. + + * All include files that may be needed (public and some others + included by the public headers) by application developers are + now copied to the ./includes directory. It does not copy any + internal headers. Affected file Makefile.defines.pre and all + Makefile.am's under lib/ and subdirs. + +Thu Jul 12 17:49:31 EEST 2001 Pekka Riikonen + + * Do not change the ~/.silc directory's permissions automatically. + Affected file irssi/src/silc/core/clientutil.c. + +Thu Jul 12 10:18:40 EEST 2001 Pekka Riikonen + + * Do not cancel the protocol in silc_server_close_connection + it might cause recursion. Now cancelled in the function + silc_server_free_sock_user_data. Affected file silcd/server.c. + + * Fixed the silc_server_remove_clients_by_server to regenerate + the channel keys correctly finally. Added also new function + silc_server_remove_clients_channels to actually do it. + Affected file silcd/server.c. + + * Fixed the silc_server_new_channel to not crash by giving + wrong router to the new channel. Affected file is + silcd/packet_receive.c. + +Wed Jul 11 18:31:57 EEST 2001 Pekka Riikonen + + * Added SilcClientParams structure to the lib/silcclient/silcapi.h + which is given as argument to the silc_client_alloc now. + It can be used to configure the client and set various parameters + that affect the function of the client. + + * The USERS command in server did not check whether the channel + is private or secret. Affected file silcd/command.c. + + * Added new argument to the USERS command in protocol specification. + The USERS command now can take the channel name as argument + as well. Added support for this in client and server and + updated the protocol specs. + + * Completed the GETKEY command in client. It can be now used + to fetch also servers public key not only some clients. + Affected files lib/silcclient/command[_reply].c. + + * Added silc_client_get_server to return server entry by the + server name. Affected files lib/silcclient/silcapi.h and + idlist.c. + + * Redefined the IDENTIFY command in protocol specification to be + more generic. It now can be used to query information about + any entity in the SILC Network, including clients, servers and + channels. The query may be based either the entity's name + or the ID. Added support for this in both client and server. + + Affected files silcd/command.c and lib/silcclient/command.c + and command_reply.c. + + * Optimized the WHOIS and WHOWAS commands in the server. Removed + the _from_client and _from_server functions. Affected file + silcd/command.c. + + * Added silc_client_get_channel_by_id_resolve to the file + lib/silcclient/silcapi.h to resolve channel information by + its ID. Added also silc_client_get_channel_by_id that + does not resolve it from the server. + +Tue Jul 10 18:05:38 EEST 2001 Pekka Riikonen + + * Added SilcServerEntry context into the client library + to represent one server. The INFO command now allocates + these to save the resolved server info. For now on the + client library will also keep information about servers, + connected and resolved with INFO. + + The INFO command now allocates the SilcServerEntry context + and saves the server info there. The COMMAND_REPLY in + the INFO now returns the parameters to application in + same order as defined in the protocol specification. + + The entries are cached in the client->server_cache. + + * The INFO command is now issued after received the Client ID + from the server. Affected file lib/silcclient/client.c. + + * The CMODE_CHANGE notify may now return also an SilcServerEntry + to the application as the mode changer might be server. + It is guaranteed that NULL is not returned anymore to the + application. Affected file lib/silcclient/client_notify.c. + + The ID Type is now also passed to the application so that + it can check whether the returned entry is SilcClientEntry + or SilcServerEntry. + + Added new function silc_client_get_server_by_id to return + the server entry by ID. Affected files are the + lib/silcclient/silcapi.h and lib/silcclient/idlist.c. + + * Do not create the channel in the Irssi SILC Client when issuing + the JOIN command but when received the sucessful JOIN command + reply. Otherwise the channel might get created even though we + could not join it. The Affected file is + irssi/src/silc/core/[silc-channels.c/client_ops.c]. + + * Fixed a channel joining bug in router. The router must also + check the channel modes, invite and ban lists etc. when serving + the JOIN command sent by normal server. Affected file is + silcd/command.c. The router now resolves the client's + information from the server who sent the JOIN command if it + does not know it, and processes the JOIN command only after + that. + + * Changed the SilcCommandCb to take new argument; void *context2. + Affected file lib/silccore/silccommand.h + + The second argument in the command callbacks in the server now + includes the SilcServerCommandReplyContext if the command was + called as pending command callback from the command reply. + Otherwise it is NULL. When called as pending the status of the + command reply will be checked and if it was erronous the + error will be sent to the original sender of the command. + This way the client always receives the error messages even + though the server was actually the one who received the error + when it resent the command to router, for example. Affected + files silcd/command[_reply].[ch]. + + * Fixed sending WHOWAS command's error message to client if + the requested client could not be found. It was missing. + silcd/command.c. + + * Changed the CMODE and CUMODE commands reply arguments in the + protocol specification. The Channel ID is now sent in both + of the commands to identify the channel. Implemented this + new feature to the client and server. Affected files + lib/silcclient/command_reply.c and silcd/command.c. + + * Made better checks for invite and ban lists in the JOIN + command in server. Affected file silcd/command.c. + +Mon Jul 9 18:28:34 EEST 2001 Pekka Riikonen + + * The server now performs the incoming host IP/DNS lookup + using the silc_socket_host_lookup and thus does not block + the server anymore. Affected file silcd/server.c. + + * Completed the multi-thread support for SILC Scheduler in + the lib/silcutil/silcschedule.c. + + * Fixed the configure.in.pre to detect the pthread correctly + on various systems. + + * Fixed a deadlock in silc_task_queue_wakeup in the file + lib/silcutil/silctask.c. + +Mon Jul 9 13:40:03 EEST 2001 Pekka Riikonen + + * Added new function silc_schedule_wakeup that is used in + multi-threaded environment to wakeup the main thread's + schduler. It needs to be used when a thread adds a new task + or removes a task from task queues. After waking up, the + scheduler will detect the task queue changes. If threads + support is not compiled in this function has no effect. + Implemented the wakeup mechanism to both Unix and WIN32 + systems. Affected files are lib/silcutil/silcschedule.[ch], + lib/silcutil/unix/silcunixschedule.c and the + lib/silcutil/win32/silcwin32schedule.c. + + * Added new function silc_task_queue_wakeup to wakeup the + scheduler by the specified task queue. Affected file + lib/silcutil/silctask.[ch]. + + * The silc_socket_host_lookup_start now wakes up the scheduler + after adding the timeout task. Affected file is + lib/silcutil/silcsockconn.c. + + * The silc_socket_host_lookup is synchronous now if the threads + support is not compiled in. However, the callback is still + called asyncronously through the scheduler, anyway. Affected + file lib/silcutil/silcsockconn.c. + +Mon Jul 9 00:24:45 EEST 2001 Pekka Riikonen + + * Added new function silc_socket_host_lookup to perform + asynchronous IP and FQDN lookups for the socket connection. + Affected files lib/silcutil/silcsockconn.[ch]. + +Sun Jul 8 18:44:53 EEST 2001 Pekka Riikonen + + * Added SILC_MUTEX_DEFINE to define the mutex on environments + that may or may not compile the mutex support in. + + Changed the silc_mutex_alloc interface. It allocates the + mutex now to the sent pointer and returns TRUE or FALSE. + + Affected file lib/silcutil/silcmutex.h. + + * Wrote the SILC Task Queue interface to support multi-threads. + Affected file lib/silcutil/silctask.[ch]. + + * Wrote the SILC Scheduler to support multi-threads. Affected + file lib/silcutil/silcschedule.c. + +Sun Jul 8 11:16:01 EEST 2001 Pekka Riikonen + + * Implemented the SILC Mutex API and SILC Thread API for WIN32 + in lib/silcutil/win32/. + +Sun Jul 8 00:18:15 EEST 2001 Pekka Riikonen + + * Defined SILC Mutex API and SILC Thread API and implemented + them for Unix. Affected files are + lib/silcutil/silcmutex.h, lib/silcutil/silcthread.h, + lib/silcutil/unix/silcunixmutex.c and + lib/silcutil/unix/silcunixthread.c. + +Sat Jul 7 14:40:31 EEST 2001 Pekka Riikonen + + * Fixed the silc_server_remove_clients_by_server's channel + key re-generation. The hash table handling was incorrect + and would not work with many channels. Affected file is + silcd/server.c. + + * Fixed some memory leaks around the server code. + + * Rewrote the silc_server_get_users_on_channel to support IPv6 + based Client ID's. Affected file silcd/server.c. + + * Defined the SILC_MESSAGE_FLAG_SIGNED to the protocol + specification. However, a separate document must be written + to define the detailed signing procedure and the payload + associated with the flag. Defined the flag to the + lib/silccore/silcchannel.h as well. + +Fri Jul 6 18:26:31 EEST 2001 Pekka Riikonen + + * Changed the dynamic tables to static size tables in the + lib/silccrypt/silchmac.c. + + * Removed GCC dependencies from the code. A patch by cras. + +Fri Jul 6 09:39:35 EEST 2001 Pekka Riikonen + + * Do not show the error "Error receiving packet bla bla" + in server if it really was not an error (-2 means that reading + is pending). Affected file silcd/server.c. + +Thu Jul 5 21:22:32 EEST 2001 Pekka Riikonen + + * Fixed a possible crash in silc_server_remove_clients_by_server + in silcd/server.c. Fixed there also some memory leaks. + + * Fixed the silc_idlist_replace_client_id. It could replace + wrong key in the hash table. Affected file silcd/idlist.c. + + * Do not check whether there are global users on the channel + if the channel->global_users is FALSE. Affected functions + silc_server_remove_from_one_channel and + silc_server_remove_from_channels in silcd/server.c. Also, + do not check if the removed client is local as we can be + sure that global client was not removed from the channel + and checking for global users is not needed. + + * The silc_server_remove_clients_by_server now re-generates + the channel keys correctly for those channels that had + clients removed from them. Affected file silcd/server.c. + +Tue Jul 3 11:39:20 EEST 2001 Pekka Riikonen + + * Found the reason of random crashes in the server. We weren't + ignoring the SIGPIPE signal (which can be sent in write()) + and it crashed the server. Affected file silcd/silcd.c. + +Fri Jun 29 20:05:25 EEST 2001 Pekka Riikonen + + * Assure that sock->user_data is not NULL in the function + silc_server_packet_send in silcd/packet_send.c. + + * Disconnect the remote connection if it could not be added + to any ID lists in the server. The affected file is + silcd/server.c. + + * Check in silc_server_packet_send[_real/dest] that the + socket is not disconnecting and ignore the data if it is. + Affected file silcd/packet_send.c. + + * Define inline to __inline on native WIN32 compilation. + Affected file includes/silcwin32.h. + + * Added some explicit type casts for inline code since MSVC + require them. Affected files lib/silcutil/silcbuffer.h, + lib/trq/silcdlist.h and lib/trq/silclist.h. + + * Print warning in log files from now on if the packet + decryption fails. Affected file silcd/server.c. + +Thu Jun 28 21:30:39 EEST 2001 Pekka Riikonen + + * Changed the `say' client operation's interface to accept + new `type' argument to indicate the type of the message sent + by the library. The application may filter the library's + messages according the type. The affected file is the + lib/silcclient/silcapi.h. + + * Added two new functions to lib/silcclient/silcapi.h: + silc_client_del_client and silc_client_del_client_by_id. + Affected file lib/silcclient/idlist.c. + + * Moved the clientincludes.h from includes/ to silc/ and + serverincludes.h from includes/ to silcd/. + + * The modes for the CMODE and CUMODE are now passed as + uint32 for application with COMMAND_REPLY. The affected + file is lib/silcclient/command_reply.c. + +Wed Jun 27 22:24:47 EEST 2001 Pekka Riikonen + + * /WHOIS without arguments shows client's own information. + Affected file lib/silcclient/command.c. + + * Changed PING to not accept any arguments. The specs + says that client can ping only the connected server so + requiring an argument is not needed. Affected file is + lib/silcclient/command.c. + +Wed Jun 27 00:10:33 EEST 2001 Pekka Riikonen + + * Fixed a fatal bug in private message sending and reception + encryption and decryption when using private message keys. + The implementation was incorrect and did not follow the + specification. It causd that some of the message were + lost since it did not use the sending and receiving keys + as the protocol suggests. This has been fixed and will cause + incompatibilities with older clients when sending private + message encrypted with private message keys. Affected files + lib/silcclient/client_prvmsg.c, lib/silcclient/client_keyagr.c + and various other in Irssi SILC Client. + + Added `responder' boolean argument to the functions + silc_client_add_private_message_key[_ske] to indicate when + the key is added as responder or initiator of the key + negotiation. + +Tue Jun 26 19:23:07 EEST 2001 Pekka Riikonen + + * Removed the silc_ske_check_version function and created + a SilcSKECheckVersion callback. Added also a function + silc_ske_set_callbacks that is now used to set all SKE + callbacks. The callback functions are not given to + the SKE functions anymore, but this function is used to + set the callbacks. + + * Fixed the WIN32 DLL generation in lib/Makefile.am.pre. + + * Added `silc_version' argument to the silc_client_alloc + to define the version of the application for the library. + The library will use the version string to compare it + against the remote host's (usually a server) version + string. Affected file lib/silcclient/silcapi.h + + * Added the KE protocol context to Key Agreement context + in client library so that we can abort the SKE if it + is in process when we get timeout. Affected file is + lib/silcclient/client_keyagr.c. + + * Do not resolve the client ID forever if it returns in the + first time that such client does not exist. This was done + for example with private message. Affected file is + lib/silcclient/client_prvmsg.c. + +Mon Jun 25 21:42:51 EEST 2001 Pekka Riikonen + + * Do not add regex.h for WIN32. The affected file + includes/silcincludes.h. + + * Added WIN32 DLL generation to lib/Makefile.am.pre. It might + not work yet 100%. It generates the DLL's automatically + when compiling with --with-win32 under cygwin. + +Sun Jun 24 19:49:23 EEST 2001 Pekka Riikonen + + * lib/contrib/regex.c is not compiled on WIN32. + + * Added silc_net_get_socket_opt function to the + lib/silcutil/silcnet.h. + + * Added includes/silcwin32.h for WIN32 specific includes + and definitions. + + * Do not use ptime structure or any of the posix process + functions on WIN32 in lib/silccrypt/silrng.c. + + * Added silc_gettimeofday to provide generic function + for struct timeval on all platforms. Added the function + to lib/silcutil/silcutil.h. + +Sun Jun 24 12:19:52 EEST 2001 Pekka Riikonen + + * Moved the lib/silccore/silcsockconn.[ch] to the utility + library as they clearly belong there. As a plus side we + can make the actual socket connection routines platform + specific. + + Added also new generic function silc_socket_read and + silc_socket_write (that used to be silc_packet_[read/write]. + The implementation of these are platform specific. + + * Added WIN32 specific routines of silc_socket_[read/write] + to lib/silcutil/win32/silcwin32sockconn.c. + +Sat Jun 23 16:01:00 EEST 2001 Pekka Riikonen + + * Added preliminary support for native WIN32 compilation under + cygwin (using the -mno-cygwin option for GCC) to the + ./configure.in.pre. The --with-win32 now prepares the + compilation for native WIN32. + + * Rewrote the SILC Scheduler interface in the file + lib/silcutil/silcschedule.h. The scheduler is now context + based and does not have anymore any global static scheduler. + Moved the Unix scheduler to the lib/silcutil/unix/ directory + and created lib/silcutil/win32 directory for WIN32 based + scheduler. + + * Added Unix specific network routines to the + lib/silcutil/unix/silcunixnet.c and the old + lib/silcutil/silcnet.c includes now only generic routines. + + Added WIN32 specific network routines to the + lib/silcutil/win32/silcwin32net.c. + + * Added Unix specific utility functions from the + lib/silcutil/silcutil.c to lib/silcutil/unix/silcunixutil.c. + + * Added WIN32 SILC Scheduler to the file + lib/silcutil/win32/silcwin32schedule.c. The code is of course + untested. + +Fri Jun 22 10:44:14 EEST 2001 Pekka Riikonen + + * Do not handle JOIN notify in the server if the target client + is not registered (idata->registered == FALSE). The affected + file is silcd/packet_receive.c. + + * Update the nickrec->founder in event_cumode in the Irssi SILC + client. Affected file irssi/src/silc/core/silc-channels.c. + + * Fixed the CUMODE_CHANGE notify handling in the server when + server and router are announcing their clients on channels. + Now the mode changes are saved and notified correctly. The + affected file is /silcd/packet_receive.c. + + * Fixed silc_idlit_replace_[server/client/channel]_id functions. + They really did not replace the cache entry in the ID Cache. + Now they do that. Affected file silcd/idlist.c. + + * Fixed the KICK notify handling in the Irssi SILC client to + update the channel records so that the kicked client does not + appear to be on the channel. The affected file is + irssi/src/silc/core/silc-channels.c. + + * Always update the conn->current_channel when executing command + on a channel. Affected file irssi/src/silc/core/silc-servers.c. + + * Fixed the KILL notify handling in Irssi SILC client to remove + the killed client on all channels. + +Thu Jun 21 17:10:08 CEST 2001 Pekka Riikonen + + * Fixed the silc_parse_command_line to remove extra spaces + from the start and end of the arguments. Affected file is + lib/silcutil/silcutil.c. + + * Cancel and free any active protocol in the function + silc_server_close_connection. Affected file silcd/server.c. + + * Cancel and free any active protocol in the function + silc_client_close_connction. Affected file is + lib/silcclient/client.c. + + * Do not execute the KILL command for clients that are in + history (ie. they are not in the network). Affected file is + silcd/command.c. + + * Fixed KILL notify handling, client does not crash anymore. + Affected file irssi/src/silc/core/silc-channels.c. + + * Reduced the default packet buffer size from 2048 to 1024 in + lib/silccore/silcpacket.c. + + * Added SILC_SKE_STATUS_FREED SKE status type and a reference + counter to the SKE context that is incresed when the SKE library + performs async operation outside the library. If the outside + process frees the SKE context and FREED status will be set + and the library will detect after the sync operation that the + libary is freed. The affected files are + lib/silcske/silcske[_status].[ch]. + + * Resolve the client entry information in the function + silc_client_channel_message to assure that NULL pointer is not + passed as client entry to the application. */ + + * Fixed the task timeout calculation to assure that there is + never negative timeouts. The affected file is + lib/silcutil/silcschedule.c. + + * Fixed the channel user mode notification sending in server. + It was sent point-to-point to the router (or to server by router) + but it needs to be destined to a channel. The routines now + supports sending the channel user mode notifys to the channels + when announcing clients and channels. Affected files are + silcd/server.c and silcd/packet_receive.c. + + * Fixed the CHANNEL_CHANGE notify handling in the client libary. + It did not actually replace the old channel entry in the cache. + Affected file lib/silcclient/client_notify.c. + +Tue Jun 19 22:10:36 EEST 2001 Pekka Riikonen + + * Fixed a possible crash in silc_packet_send_prepare. It now + assures always that there is enough space in the buffer and + at the tail area of the buffer (for MAC). + + Fixed the inbound buffer reallocation in silc_packet_read. + It was old code and did not handle the reallocation correctly. + Affected + + The affected file is lib/silccore/silcpacket.c. + + * Fixed buffer overflow in silc_parse_nickname in the file + lib/silcutil/silcutil.c. + +Tue Jun 19 13:40:09 CEST 2001 Pekka Riikonen + + * make install generates new server keys only if there is not + keys already. + +Mon Jun 18 18:49:07 EEST 2001 Pekka Riikonen + + * Set SILC_MESSAGE_FLAG_NOREPLY when sending the away message. + Added check that if the NOREPLY is set then we will not send + the away message. This avoids infinite loop of away messages + if both clients are away. The affected file is + lib/silcclient/client_prvmsg.c. + + * Fixed client crash if /NICK was given without arguments. + Affected file lib/silcclient/command.c. + + * Server does not send the invite list in INVITE command back + to the client if the list was not altered. Added this notion + to the protocol spec as well. Affected file silcd/command.c. + + Fixed possible crash in INVITE command by checking the + value of silc_server_get_client_route command. + + * Fixed the INVITE notify type handling. The arguments are now + taken in correct order and client does not crash. The affected + file is irssi/src/silc/core/silc-channels.c. + + Removed the "Inviting xxx to channel" message from the + client library away and let the application handle it. + Affected file lib/silcclient/command.c. Added that message + to Irssi SILC client's message formats. + + * Fixed CMODE command crash in client. It now checks the + amount of arguments correctly and does not crash. The affected + file is lib/silcclient/command.c. + + * Do not create new channel automatically in silc_channels_join + but check whether the channel by that name already exists. + Affected file irssi/silc/core/silc-channels.c. + + * Do not send the SERVER_SIGNOFF to router if the disconnected + entity was the router. Affected file silcd/server.c. + + * Added the handling of the SERVER_SIGNOFF notify to the Irssi + SILC client as it was missing from there. + + Added the handling of the KICK notify to the Irssi SILC client + as it was missing. Added "you have been kicked" message to + Irssi SILC client's message modules formats. + + Added the handing of the KILL notify to the Irssi SILC client + as it was missing. Added the kill message module formats + as well. + + The affected file is irssi/src/silc/core/silc-channels.c. + + * The router did not save the channel mode the server announced. + Affected file silcd/packet_receive.c. + + * Fixed a possible crash in INFO command in server. If the + server did not provide the server info it crashed. Affected + file silcd/command.c. + +Sun Jun 17 15:26:05 EEST 2001 Pekka Riikonen + + * Fixed the GETKEY command in the server to check also the + global list. Otherwise the GETKEY would not work correctly + in normal SILC server. Affected file silcd/command.c. + +Sat Jun 16 18:00:00 EEST 2001 Pekka Riikonen + + * Fixed GETKEY crash, it crashed if the command did not succseed. + +Tue Jun 12 21:36:18 EEST 2001 Pekka Riikonen + + * Redefined the SILC MP API in lib/silcmath/silcmp.h. The API + is now real and not just an macro interface to GMP. + + Removed the entire GMP from the source tree and imported new + NSS MPI library instead. Reason for removing GMP is that it is + extremely large and compiles extremely slow. The NSS MPI + is only a few files and compiles in less than 10 seconds. + The speed is also about the same as GMP. The MPI is imported + to lib/silcmath/mpi. + + If the system has GMP installed we will still use the GMP. + If it is not then the NSS MPI will be compiled. + +Mon Jun 11 18:07:24 EEST 2001 Pekka Riikonen + + * Merged a long nickname (127 characters long) crash bugfix from + Irssi CVS tree. Affected file irssi/src/core/misc.c. + + * Merged a freed memory reference bugfix from Irssi CVS tree. + Affected file irssi/src/core/commands.c. + +Sun Jun 10 16:08:35 EEST 2001 Pekka Riikonen + + * Added the server's public key sving and verification to the + server when performing the SKE. This was missing and the + remote server's (or router's) public key was accepted without + checking whether we have it previously or trust it at all. + Affected file silcd/protocol.c. + +Sat Jun 9 20:17:30 EEST 2001 Pekka Riikonen + + * Check in the silc_server_timeout_remote if protocol is active + and make sure that the protocol's final callback is called so + that all memory if freed. Affected file silcd/server.c. + +Sat Jun 9 12:51:27 EEST 2001 Pekka Riikonen + + * silc_server_whois_send_reply crashed the server if the nickname + was 127 characters long. Affected file silcd/command.c. + +Thu Jun 7 16:29:56 EEST 2001 Pekka Riikonen + + * Added sanity check to the silc_server_new_client. If the hostname + is provided inside username then check that the provided hostname + really is the same as the resolved one. If the hostname was not + resolved then check it from the public key. Affected file is + silcd/packet_receive.c. + + * Fixed a fatal bug in Irssi SILC client. Do not send QUIT command + if the server disconnected us and the connection is not valid + anymore. Affected file irssi/src/silc/core/silc-channels.c. + + * Moved the silc_client_[chmode|chumode|chumode_char] away from + the library to the lib/silcutil/silcutil.[ch]. + +Thu Jun 7 08:57:16 CEST 2001 Pekka Riikonen + + * Close log file after open. Affected file + lib/silcutil/silclog.c. + + * Check whether sock == NULL in silc_client_send_packet and return + if it is. Affected file lib/silcclient/silcclient.c. + + * Check rec->entry == NULL in the Irssi SILC Client before + sending the channel message. Affecte file is + irssi/src/silc/core/silc-servers.c. + +Tue Jun 5 08:08:21 CEST 2001 Pekka Riikonen + + * Merged a splitted window bugfix from Irssi CVS tree. The + affected file is irssi/src/fe-text/textbuffer-view.c. + + * Fixed the ME, ACTION and NOTICE printing in Irssi Client. + It did not print nickname. + + * Improved the distributions system a bit. + +Mon Jun 4 17:57:16 CEST 2001 Pekka Riikonen + + * Merged /WINDOW bugfix from irssi CVS tree. Affected file is + irssi/src/fe-text/gui-window.c. + + * Fixed a fatal bug in Irssi SILC client. Crashed if sent message + to in-active server. The affected file is + irssi/src/silc/core/client_ops.c. + + * Resolve the client in USERS command reply if the entry does + not have username resolved. The affected file is + lib/silcclient/command_reply.c. Also, changed the IDENTIFY + command to WHOIS command to really resolve stuff. The USERS + is not used any more in any critical section so WHOIS can + be used even though it might be slower than IDENTIFY. + + * Changed the lib/silcutil/silchashtable.h header to ROBODoc + format. + +Sun Jun 3 14:21:32 EEST 2001 Pekka Riikonen + + * Changed the protocol API a bit more consistent in the + lib/silccore/silcprotocol.[ch]. + + * Changed the following headers to ROBODoc format: + + lib/silccore/silcpayload.h + lib/silccore/silcprotocol.h + lib/silccore/silcsockconn.h + + All core library headers are now formatted. + +Sat Jun 2 10:45:09 EEST 2001 Pekka Riikonen + + * Fixed a bug in Irssi SILC client; do not show that you are + server/router operator if you really are not. Affected file is + irssi/src/silc/core/client_ops.c. + + * Renamed silc_command_free_payload to silc_command_payload_free. + Affected file lib/silccore/silccommand.h + + * Added silcmath.h to include the prototoypes of various routines + in the lib/silcmath. Removed the old modinv.h, mpbin.h and + silcprimegen.h. + + * Changed the following headers to ROBODoc format: + + lib/silccore/silcchannel.h + lib/silccore/silccommand.h + lib/silccore/silcid.h + lib/silccore/silcidcache.h + lib/silccore/silcmode.h + lib/silccore/silcnotify.h + lib/silccore/silcpacket.h + lib/silcmath/silcmath.h + +Fri Jun 1 22:19:37 EEST 2001 Pekka Riikonen + + * Added checking to the server code not to start the server if + ciphers and stuff are not configured properly. Affected files + silcd/serverconfig.[h] and silcd/server.c. + + * Changed the layout of the header files of the public interfaces + in the SILC libraries. The new layout supports ROBODoc + documentation tool (and some others) so that it is easy to create + a library reference manual. All the other headers and source + code must still follow the CodingStyle document. Also source + code must not include these ROBODoc stuffs, only the headers. + Furthermore, all public interface headers must now be named + by using `silc' prefix, example: silcapi.h, silccipher.h. + Some files were renamed due to this. All the other headers + must not be used as public interfaces. I will update the + CodingStyle document later. Changed following headers, so far: + + lib/silcclient/silcapi.h + lib/silccore/silcauth.h + lib/silccore/silcprivate.h + lib/silccrypt/silcdh.h + +Fri Jun 1 10:28:09 EEST 2001 Pekka Riikonen + + * Updated TODO. + + * Removed silc_client_packet_send_flush from the client library + as it is not needed. Affected file lib/silcclient/client.[ch]. + + * Added printing of message of unresolved authentication method + to the Irssi SILC client. Added it to the module formats. + Removed the same message from the client library. + +Thu May 31 13:57:33 CEST 2001 Pekka Riikonen + + * Added new distribution feature, DISTLABEL. Every distribution + can define own preprocessor label that can be used in the + source code. For example: #ifdef SILC_DIST_CLIENT. Affected + file distributions, acconfig.h.pre and prepare. + +Tue May 29 22:16:40 EEST 2001 Pekka Riikonen + + * Added Makefile.defines_int to include the actual definitions + for Makefile.defines.in. Tested the new distribution system, + created distributions and tested installation. + + * Added AWAY message printing to the Irssi SILC client. Added + the messages to the irssi/src/fe-common/silc/module-formats.[ch]. + + * Added SCONNECT command to call the SILC's CONNECT command. + Cannot use CONNECT directly since Irssi uses that internally. + Affected file irssi/src/silc/core/silc-servers.c. + + Added ACTION local command. It is same as ME command but takes + the channel as mandatory argument. + + Rewrote some of the Irssi's help files to suite for SILC + protocol. + +Mon May 28 19:05:22 EEST 2001 Pekka Riikonen + + * Added Makefile.defines[.in] that should for now on be included + in all Makefile.am file in the source tree. That file includes + all common compilation definitions for SILC source tree. + +Mon May 28 10:30:51 EEST 2001 Pekka Riikonen + + * Minor changes to the ./prepare script to change the package + name according the distribution name to the configure.in. + +Sun May 27 22:24:57 EEST 2001 Pekka Riikonen + + * Created new distribution system. Added file `distributions' + that defines all the distributions that can be created out of + the SILC source tree. The ./prepare script now reads that + file to determine how to prepare the distributions. The + first argument to the ./prepare is the name of the distribution + and second is the version of the distribution. If given + without arguments it creates the default (toolkit) distribution + with the default version (defined in ./prepare). + + All Makefile.am files that are subject to the distributions + are now named as Makefile.am.pre. These are ./Makefile.am + and lib/Makefile.am. Others may be changed later. + +Sun May 27 15:57:17 EEST 2001 Pekka Riikonen + + * Added invite list, ban list, some key management and connection + error message printing to module formats in the Irssi SILC client. + + * Added new silc_client_set_away_message to set the away message + that is back to the person who sent private message. The + affected file lib/silcclient/silcapi.h and the + lib/silcclient/client_prvmsg.c. + +Sun May 27 12:39:48 EEST 2001 Pekka Riikonen + + * Fixed the private message sending in the Irssi SILC client, + added local command KEY to the Irssi SILC client. + + Added key management and key agreement message formats to the + irssi/src/fe-common/silc/module-formats.[ch]. + + Added USERS (alias WHO) printing, server/router operator + indication and LIST command printing to the module formats. + +Sat May 26 17:43:42 EEST 2001 Pekka Riikonen + + * Fixed channel joining notify handling, cumode notify handling + from Irssi SILC client. + + * Added SILC specific module-formats to the Irssi SILC client so + that SILC specific message hilighting, colors etc is possible. + Affected file irssi/src/fe-common/silc/module-formats.[ch]. + + Added channel mode, channel user mode, actions, notices, + whois and whowas printing to the the module-formats.c. + + * Fixed a bug in channel deletion in the server. The channel + is not left to the cache even if the channel founder auth mode + is set when there are no users anymore on the channel. Affected + file silcd/server.c. + + * The silc_net_localhost now resolves the entire hostname including + the domain name. Affected file lib/silcutil/silcnet.c. + +Sat May 26 12:13:37 EEST 2001 Pekka Riikonen + + * Changed the ask_passphrase client operation to be ascynchronous. + It has now a completion callback and a context that the + application must call after it has got the passphrase from + the user. Affected files lib/silcclient/silcapi.h, + lib/silcclient/protocol.c, lib/silcclient/command.c and + silc/client_ops.c. + + Added SilcAskPassphrase callback that the application calls + to deliver the passphrase to the library. + + * Changed the SKE protocol's SilcSKEVerifyCb to be asynchronous. + The public key verification and especially a certificate + verification is asynchronous procedure. + + Added new SILC_SKE_STATUS_PENDING status to indicate the + request is pending and a callback will be called to finalize + the request. + + Added also SILC_SKE_STATUS_PUBLIC_KEY_NOT_PROVIDED status to + indicate that remote end did not send its public key (or + certificate), even though we require it. Added check for this + condition in the SKE. This was a security bug, now fixed. + + Defined new SilcSKEVerifyCbCompletion callback that is called + when the verification process is completed. + + The affected files lib/silcske/silcske_status.h and + lib/silcske/silcske.[ch]. + + * Changed the verify_public_key client operation to be async + as well. Defined SilcVerifyPublicKey callback that is used to + indicate the success of the public key verification process. + + Changed the server and client to use the new async client + operations. + + * Changed the Irssi SILC client's internal scheduler to be called + twice as many times as it used to be. As a result the client + should be a bit faster now. Affected file is + irssi/src/silc/core/silc-core.c. + + * Added support to Irssi SILC client of asynchronous public key + verification and passphrase inquiry. Affected file is + irssi/src/silc/core/silc-core.c. + +Fri May 25 14:38:38 EEST 2001 Pekka Riikonen + + * Do not say "You have left channel %s" in client library. + Moved it to the application. Affected files are + lib/silcclient/command.c and silc/client_ops.c. + + * Fixed silc_client_get_clients. Command context was not + duplicated and was freed memory in the callback. Affected + file lib/silcclient/idlist.c. + + * Do not say "you are now talking..." on JOIN command in the + client library. The appliation must handle it. + + * Do not say ".. changed topic to" in command reply in the + client libary. The application must handle it. + + * Fixed TOPIC command sending in the client library. + + * Fixed a memory leak in silc_client_command_free in the file + lib/silcclient/command.c. + +Thu May 24 19:08:55 EEST 2001 Pekka Riikonen + + * Imported a modified version of Irssi client to the source tree. + The Irssi will be used to create a new client called + Irssi SILC. Imported to irssi/. + + Added silc_core_init_finish function to the Irssi. Affected + file irssi/configure.in. + + A lot changes in the Makefile.ams around the irssi tree. + +Tue May 22 22:23:49 EEST 2001 Pekka Riikonen + + * Do not rehash if the new size is same as the old size of the + hash table, in the silc_hash_table_rehash*. The affected file + lib/silcutil/silchashtable.c. + + * Replaced hash_table_del_by_context calls from the server + (when channel->user_list and client->channels) to the + hash_table_del as it is sufficient and faster. + +Tue May 22 17:27:16 EEST 2001 Pekka Riikonen + + * Added silc_hash_table_list, silc_hash_table_get and the + SilcHashTableList structure to provide an alternative way to + traverse the hash table. The affected files are + lib/silcutil/silchashtable.[ch]. + + * Changed the server's idlist routines to use the hash table + routines to optimize the code. + +Mon May 21 21:46:20 EEST 2001 Pekka Riikonen + + * Replaced the client entry's `channel' list and channel entry's + `user_list' list to hash tables for optimized lookup. Changed + the code to use the hash table interface around the code. + Affected file lib/silcd/idlist.[ch]. + + * Added `auto_rehash' boolean argument to the function + silc_hash_table_alloc to indicate whether the hash table should + auto-rehash when it thinks is appropriate time. It will + increase the hash table size if the there is twice as much + entries in the table than the size of the table, and will + decrease the size if there are twice as less entries than + the size of the table. + +Mon May 21 09:51:11 EEST 2001 Pekka Riikonen + + * Fixed silc_xxx_get_supported to not crash at some circumstances. + +Sun May 20 13:45:58 EEST 2001 Pekka Riikonen + + * silc_idcache_purge_by_context deletes the entry now by context + as it is supposed to do. Affected file lib/silccore/idcache.c. + + * Send the ERR_NO_SUCH_NICK in the WHOIS command reply if the + client is not anymore valid (WHOWAS givens the info) and not + the ERR_NO_SUCH_CLIENT_ID if the nickname still exists. + +Sat May 19 16:30:03 EEST 2001 Pekka Riikonen + + * Removed the `data' and `data_len' arguments from the ID Cache + interfaces and added `name' argument. ID Cache does not handle + anymore the binary data only a names associated with given ID. + + * When hashing a Client ID with silc_hash_id the entire ID is + not hashed anymore, instead only the hash of the Client ID is + hashed. This way we can access the Client ID from the cache + with Client ID but with the hash of the ID (which is a hash of + the nickname) as well without any difference in performance. + + Added also silc_idcache_find_by_id_one_ext to do one on one + searching when we have the actual ID. Added also function + silc_hash_client_id_compare. The affected files are + lib/silccore/idcache.[ch] and lib/silcutil/silcutil.[ch]. + + * When hashing the name associated with a ID it is always done + in lowercase. This way we can access the cache without worrying + about case-sensitivity, even though, for example nicknames are + case sensitive. + + * Fixed a bug in server with channel message sending. It put + wrong ID type as destination ID. The affected file + silcd/packet_send.c. + + * silc_idcache_del_by_context now deletes from all hash tables + by context. Affected file lib/silccore/idcache.c. + +Fri May 18 17:42:00 EEST 2001 Pekka Riikonen + + * Changed the client library to use the new ID Cache interface. + Changes around the source tree. + + * Added silc_hash_table_rehash_ext to rehash with specific + hash function. Affected file lib/silcutil/silchashtable.[ch]. + + * Added silc_hash_string_compare to compare two strings in the + hash table. Affected file lib/silcutil/silcutil.[ch]. + +Fri May 18 11:18:45 EEST 2001 Pekka Riikonen + + * Added new function silc_idcache_del_by_context into the + lib/silccore/idcache.[ch]. + + * Changed the server's ID list routines to use the new ID Cache + interface. Changes around the source tree. + +Fri May 18 08:35:31 EEST 2001 Pekka Riikonen + + * Added silc_hash_table_del[_by_context]_ext functions in to the + lib/silcutil/silchashtable.[ch]. + + Removed silc_hash_table_find_all* routines and added new + silc_hash_table_find_foreach to replace them. + + Added silc_hash_table_replace_ext function as extended + replacing function. Separated the simple hash table interface + from the extended hash table interface in the file + lib/silcutil/silchashtable.h. + + * Fixed minor bugs and changed it to use some of the new + hash table functions in lib/silccore/idcache.c + +Thu May 17 18:15:12 EEST 2001 Pekka Riikonen + + * Added new function silc_hash_table_find_all to return all keys + in the hash table by the specified key. As the hash table is + collision resistant it also makes it possible to have several + duplicate keys in the hash table. This function may be used to + find all of the keys from the hash. + + Added user_context arguments to the SilcHashFunction, + SilcHashCompare and SilcHashDestructor to deliver user specified + context. + + Added new fuctions silc_hash_table_find[_all]_ext to do + extended lookup with specified hash and compare functions and + specified user contexts. + + Added new function silc_hash_table_add_ext to add the key + with specified hash function and user context. + + Added new function silc_hash_table_foreach to traverse all + entrys in the hash table. Added SilcHashForeach callback + function. + + Added new function silc_hash_table_del_by_context to delete + the entry only if the context associated with the key matches. + + Affected files are lib/silcutil/silchashtable.[ch]. + + * Removed silc_hash_[server/client/channel]_id and added just + silc_hash_id to the lib/silcutil/silcutil.[ch]. Added also + silc_hash_id_compare to compare two ID's using as the hash table + comparison function. Added also silc_hash_data to hash + binary data and silc_hash_data_compare to compare it. + + * Removed silc_idlist_find_client_by_hash as it is not needed + anymore. Affected file silcd/idlist.[ch]. + + * Rewrote the entire ID Cache system (in lib/silccore/idcache.[ch]) + to use internally the SilcHashTable. The new ID Cache is a lot + faster than the old one. Some of the ID Cache interface was also + rewritten and obsolete and stupid functions were removed. + +Wed May 16 23:03:30 EEST 2001 Pekka Riikonen + + * Added entry_count field to the SilcHashTable to keep the number + of the entries in the table. Implemented the function + silc_hash_table_rehash. Added new function + silc_hash_table_count. Affected file lib/silcutil/silchashtable.c. + + Fixed a minor bug in silc_hash_table_free. + + * Added silc_hash_string, silc_hash_uint, silc_hash_ptr, + silc_hash_client_id, silc_hash_server_id and silc_hash_channel_id + into the lib/silcutil/silcutil.[ch]. + +Wed May 16 20:02:47 EEST 2001 Pekka Riikonen + + * Implemented a collision resistant hash table into the + lib/silcutil/silchashtable[ch]. See the header and the source + for the SilcHashTable API. + +Tue May 15 22:05:46 EEST 2001 Pekka Riikonen + + * Merged dotconf version 1.0.2 into lib/dotconf. + +Sun May 13 19:32:09 EEST 2001 Pekka Riikonen + + * Do not compile anything in lib/silcsim/* if the SIM support + is not enabled. The tree should now compile without problems + under cygwin. + +Thu May 10 22:49:51 EEST 2001 Pekka Riikonen + + * Compiled the SILC under cygwin. Compiled and tested briefly + without problems. More tests needed. The SIMs didn't compile + though. + + * Added various #ifdef HAVE_* stuff to lib/silccrypt/silrng.c. + + * Fixed possible crash in silc_get_username in the + lib/silcutil/silcutil.c. + +Tue May 8 09:04:03 EEST 2001 Pekka Riikonen + + * Fixed a va_arg in silc/client_ops.c. + + * Oops, RC5 routines were named AES and caused some problems + when not using SIM's. Affected file lib/silccrypt/rc5.c. + +Sun May 6 13:59:48 EEST 2001 Pekka Riikonen + + * Added new SilcIDIP structure into the lib/silccore/id.h and + replaced the old `ip' fields from all SILC ID's to that type. + This is a step towards IPv6 support. + + The silc_id_get_len takes now the ID as an extra argument. + The silc_id_id2str, silc_id_str2id and silc_id_dup now supports + both IPv4 and IPv6 based ID's. + + The affected files are lib/silccore/id.[ch] and other files + around the tree using these routines. + + * Removed the ID length arguments in server from various + silc_server_send_notify_* routines -> they are not needed + anymore. + +Sat May 5 13:56:33 EEST 2001 Pekka Riikonen + + * Fixed memory leak in silc_encode_pem_file in the file + lib/silcutil/silcutil.c. + +Thu May 3 21:23:50 EEST 2001 Pekka Riikonen + + * Check minor version as well in the SKE. Affected files are + silcd/protocol.c and lib/silcclient/protocol.c. + + * Added --identifier option to the server so that an identifier + can be when creating the public key for the server. Affected + file is silcd/silcd.c. + + * Fixed minor decoding bug in silc_pkcs_decode_identifier in + lib/silccrypt/silcpkcs.c. + +Wed May 2 20:50:49 EEST 2001 Pekka Riikonen + + * Register default ciphers and stuff when using -C option with + the server. Affected file sildc/silcd.c. + + * Put back the servers public key filename format, it is better + than the new one. For now, the client keys are saved with the + new filename format. The affected file silc/client_ops.c. + + * Implemented the Cipher API for the rest of the ciphers that + did not implement it or implemented it the wrong way. + +Wed May 2 13:31:26 EEST 2001 Pekka Riikonen + + * Register default ciphers and stuff when using the -S option + in the client. Affected file silc/silc.c. Same also when + creating new key pair with -C option. + +Tue May 1 14:18:13 EEST 2001 Pekka Riikonen + + * Fixed the silc_verify_public_key client operation function to + save the public keys differently. The fingerprint is now + used as filename and not the hostname. This way also the + client keys are saved uniquely and not with hostnames. The + affected file is silc/client_ops.c. + + * Trimmed the silc_hash_fingerprint function to remove extra + whitespaces from the end of the fingerprint. The affected + file is lib/silccrypt/silchash.c. + + * Updated TODO. + + * Added silc_cipher_register_default function to register all + default ciphers. It can be used when configuration files + does not exist and the application does not want any specific + ciphers in any specific order. + + The SilcDList is now used as silc_cipher_list dynamically + allocated cipher list. Removed the static list all together + and now all ciphers must be allocated to the dynamic list. + The silc_cipher_alloc routine was changed to check only the + dynamic list. + + All silc_cipher_* routines that used to return int returns + now bool. + + The affected files lib/silccrypt/silccrypt.[ch]. + + * The same thing was done to silc_hash_* as for silc_cipher_* + routines. Affected files lib/silccrypt/silchash.[ch]. + + * The same thing was done to silc_pkcs_* as for silc_cipher_* + routines. Affected files lib/silccrypt/silcpkcs.[ch]. + Added also silc_pkcs_[un]register[_default] functions. + Removed the data_context from the PKCS API. + + * Added silc_hmac_register_default function to register default + hmacs. Affected files lib/silccrypt/silchmac.[ch]. Added also + SILC_ALL_HMACS macro that can be used with silc_hmac_unregister + to unregister all hmacs at once. + + * Register the default ciphers, hash functions, PKCSs and HMACs + if client's configuration file does not exist. The affected + file silc/silc.c. + + * The client did not load the hash functions from the SIM + modules at all. Added support for this. Affected file is + silc/clientconfig.c. + + * When decoding public key with silc_pkcs_public_key_decode, check + the supported algorithm only if PKCS are registered. Affected + file lib/silccrypt/silcpkcs.c. The same was done with the + silc_pkcs_private_key_decode. + + * Fixed the SILC List routines to keep the list always in order. + It used to change the list's order when traversing the list but + not it preserves the order. Affected file lib/trq/silclist.h. + +Mon Apr 30 17:29:03 EEST 2001 Pekka Riikonen + + * Added the client library to use the SilcSocketConnection's + reference counter (by silc_socket_dup) to prevent the bug that + the socket object may be freed underneath async operation. + + * The name resolv library checking fixes in the configure.in.pre. + The patch by salo. + + * Created new version of the protocol drafts for future + development. The -03 drafts are the ones that will be changed + in the trunk now and the -02 will remain as they are. + + * Send list of CUMODE notifys to the router when announcing + the channel users to the router. Affected file silcd/server.c. + If the router receiving channel founder CUMODE for a channel + that already has channel founder it will send CUMODE notify + to the sender to remove the channel founder rights from the + announced client. Affected file silcd/packet_receive.c. + + * The CUMODE notify may now use Server ID as well as the entity + who changes the mode. Updated protocool specs. + + * Updated INSTALL and README files. + +Sun Apr 29 23:17:50 EEST 2001 Pekka Riikonen + + * New web pages in the http://silc.pspt.fi. The pages was + designed by salo. + + * Updated CREDITS. + +Sun Apr 29 13:33:41 EEST 2001 Pekka Riikonen + + * Implemented the [DenyConnectin] config section in the server. + Added silc_server_config_denied_conn to check whether incoming + connection is denied. Affected file silcd/serverconfig.[ch]. + + * Do not check the ports when checking the incoming configuration + data if the port is 0, meaning any. Affected file is + silcd/serverconfig.c. + +Fri Apr 20 18:58:43 EEST 2001 Pekka Riikonen + + * Fixed buffer overflow in silc_string_compare in the file + lib/silcutil/silcutil.c. + + * Fixed double free in silc_server_command_leave in the file + silcd/command.c. + +Fri Apr 20 14:00:11 EEST 2001 Pekka Riikonen + + * Fixed the version checking in the server. Affected file is + silcd/protocol.c. + +Thu Apr 19 19:52:46 EEST 2001 Pekka Riikonen + + * Fixed the configuration data fetching when accepting new + connections in the server. Affected file silcd/server.c. + +Thu Apr 19 11:40:20 EEST 2001 Pekka Riikonen + + * Added `sender_entry' argument to the function + silc_server_packet_relay_to_channel so that we can check + whether some destination actually belongs to the same route + the sender belongs (ie, we must not resend the packet to the + sender). Affected file silcd/packet_send.[ch]. + + * Added `servername' field to the SilcClientEntry in the server + to hold the name of the server where client is from. Affected + file is silcd/idlist.h. + +Wed Apr 18 22:19:03 EEST 2001 Pekka Riikonen + + * Moved the channel message encrypting in the router betwen + router connections from silc_server_channel_message to the + silc_server_packet_relay_to_channel since we want to check + whether we have anybody channel before encrypting anything. + Affected files silcd/packet_[receive/send].c. + +Tue Apr 17 21:18:19 EEST 2001 Pekka Riikonen + + * Fixed the [AdminConnection] server config section to support + multiple entries. Affected file silcd/serverconfig.c. + + * Added support into the server to check the validity of the + incoming connection before executing any KE or authentication + protocols. + + * The connection configuration is now saved to the KE and + connection auth protocol contexts and not fetched anymore in + the protocol. Affected files silcd/server.c, silcd/protocol.[ch]. + + * The local hosts listenning address and port is also resolved + now when starting the server. We want to have the socket object + to include the real address and port for the listener. Added + new function silc_net_check_local_by_sock into the files + lib/silcutil/silcnet.[ch]. + + * Fixed a broadcast bug in server -> do not broadcast if we + are standalone. + + * Fixed a routing bug. Do not route broadcast packets ever. + Broadcast packets must be processed always and not routed since + they may be destined to some other host than yourself and thus + would get routed without no good reason. Affected file is + silcd/server.c. + + * Added function silc_server_config_is_primary_route to check + whether primary router connection has been configured (a router + configuration that we are initiating). If there is not, we + will assume that there is only two routers in the SILC network + and we will use the incoming router connection as our primary + route. Affected files silcd/serverconfig.[ch], silcd/server.c. + + * Changed the order of the broadcasting. Broadcast _after_ the + packet has been processed not before. Affected file is + silcd/server.c. + + * Fixed a [ClientConnection] parsing bug. The port was never + parsed correctly thus resulting to port 0. Affected file + silcd/serverconfig.c. + + * Fixed silc_server_send_notify_args -> it ignored the `broadcast' + argument and did not set the broadcast packet flag. Affected + file silcd/packet_send.c. Fixed same bug in the function + silc_server_send_notify as well. + + * If we receive NEW_ID packet for our own ID in the server, ignore + the packet. + +Mon Apr 16 12:10:33 EEST 2001 Pekka Riikonen + + * Updated TODO. + + * Removed the nickname from the Private Message Payload. + Updated the code and the protocol specs. + + * Updated protocol specs for submitting to the IETF. + + * Tweaked the Random Number Generator a bit. Affected file + lib/silccrypt/silcrng.c. Exported a new function + silc_rng_[global]_add_noise which can be used to add more + noise to the RNG. + +Sat Apr 14 16:21:32 EEST 2001 Pekka Riikonen + + * Do not parse packets with different timeout when protocol + is active -> may cause problem with rekey. Affected file + silcd/server.c. + + * When server receives signoff notify it must not create + new channel key if the client is on any channels since the + sender of the signoff notify will create it. + +Fri Apr 13 17:12:46 EEST 2001 Pekka Riikonen + + * Added printing of error messages during SKE protocol from the + failure packet sent by server during SKE. Affected file + silc/client_ops.c. + + * Removed the client's failure_callback handling with timeout + and handle it immediately when received. + + * The SKE library returned wrong type in SUCCESS and FAILURE + packets. They must be 32 bit MSB not 16 bit MSB. + +Fri Apr 13 00:09:08 EEST 2001 Pekka Riikonen + + * Ok, rewrote the logic of the re-key and now it seems to work. + I tested it on high traffic with frequent re-keys without + problems. Added hmac_receive (and renamed hmac to hmac_send) + in SilcClientConnection in lib/silcclient/client.h and + in SilcIDListData in silcd/idlist.h. Also, removed the + SilcPacketParserContext's cipher and hmac fields as they are + not needed anymore and actually caused some problems when + the ciphers and hmac's changed underneath the packet parser. + +Thu Apr 12 14:42:51 EEST 2001 Pekka Riikonen + + * If re-key protocol is active then process the incoming packets + synchronously since we must assure that icoming packets encrypted + with the old key is processed before the new keys is set to + use. This is true other packets than for REKEY packets. + Affected file silcd/server.c. The same was done to client library + as well, affected file lib/silcclient/client.c. + +Thu Apr 12 12:01:52 EEST 2001 Pekka Riikonen + + * Fixed bug in client and server to accept the force send if + the packet is send from silc_[server/client]_packet_process + function. Otherwise the packets are never delivered, oops. + +Wed Apr 11 22:10:15 EEST 2001 Pekka Riikonen + + * Disable force sending of packets when REKEY protocol is active. + We must assure that no packet is sent directly when rekey is + performed. All packets must be sent through packet queue. + Added macro SILC_SERVER_IS_REKEY to silcd/server.h and + SILC_CLIENT_IS_REKEY to lib/silcclient/client.h. Affected + function is silc_[server/client]_packet_send_real to check + the situation. + + * Replaced the SIM paths from example config files to + /usr/local/modules. Also, make install creates now + /usr/local/silc/logs directory to hold all the SILC server + logs. + +Wed Apr 11 16:59:59 EEST 2001 Pekka Riikonen + + * Made the configure.in.pre work on Solaris. Patch by salo. + + * Made all ciphers compatible with non-x86 machines. Defined + CBC mode macros into lib/silccrypt/ciphers_def.h. + +Tue Apr 10 20:32:44 EEST 2001 Pekka Riikonen + + * Fixed the make install. + +Tue Apr 10 16:20:34 EEST 2001 Pekka Riikonen + + * When MAC computation fails the silc_packet_decrypt returned 0 + even though it was supposed to return -1. Fixed this. The + affected file is lib/silccore/silcpacket.c. + + * Do not replace the config files in /etc/silc (in make install) + if they already exist. Affected file ./Makefile.am. + + * Do not send re-key packets immediately but through packet queue. + Affected file silcd/protocol.c and lib/silcclient/protocol.c. + + * Changed silc_net_check_host_by_sock to return FALSE if the + IP/DNS could not be resolved. Though, it returns the IP address + now even if it could not resolve it (but returns also FALSE). + Affected file lib/silcutil/silcnet.[ch]. + +Mon Apr 9 21:54:44 EEST 2001 Pekka Riikonen + + * Added silc_pkcs_decode_identifier to decode the public key's + identifier. Affected file lib/silccrypt/silpkcs.[ch]. + Added also silc_pkcs_free_identifier. Added also new context + SilcPublicKeyIdentifier. + + * Added -S option to the silc client. It is used to dump the + contents of the specified public key file. + + * Changed the PKCS api to return the public key length when + setting the public key. + + * Fixed a fatal bug in the public and private key file loading. + Affected file lib/silccrypt/silcpkcs.c. + + * Execute the packet parsing for client with zero (0) timeout + if the protocol is active. Affected file silcd/server.c. + +Sun Apr 8 19:30:56 EEST 2001 Pekka Riikonen + + * Made the key generation options to the silcd program. Added + -C option, equivalent to client's option. + + * Added new [ServerKeys] config section to the server. It + configures the server's public and private key. + + * Defined generic Public Key Payload into the protocol + specification to send specific type of public keys and + certificates. + + * Defined new command SILC_COMMAND_GETKEY to fetch a client's + public key or certificate. + + * Implemented the GETKEY command to the server and to the + client library and on user interface. + +Sun Apr 8 01:37:21 EEST 2001 Pekka Riikonen + + * Made preliminary `make install' work. + +Thu Apr 5 17:42:30 EEST 2001 Pekka Riikonen + + * Added SilcServerRekey context into silcd/idlist.h. + + * Added the PFS support as defined in the specification to the + SKE protocol. Affected files lib/silcske/*.c. + + * Added `ske_group' field to the SilcServerRekey context to hold + the number of the SKE group that is used with PFS in re-key. + Affected file silcd/idlist.h. + + * Added PFS re-key support to the server. Affected file is + silcd/protocol.c. + + * Added silc_protocol_cancel to cancel execution of the next + state of the protocol. Affected file is + lib/silccore/silcprotocol.[ch]. + + * Added the re-key support with and without PFS to the client + library. Re-key is performed once in an hour, by default. + + Added new protocol type SILC_PROTOCOL_CLIENT_REKEY. + Added silc_client_rekey_callback and silc_client_rekey_final. + Affected files are lib/silcclient/protocol.[ch] and + lib/silcclient/client.[ch]. + + * Removed the `hmac_key' and `hmac_key_len' fields from the + SilcClientConnection structure; not needed. Affected file is + lib/silcclient/client.h. + + * Updated TODO. + +Wed Apr 4 16:32:31 EEST 2001 Pekka Riikonen + + * Do not ask whether user wants to use the negotiated private key + for private messages, just use it. Affected file is + silc/local_command.c. + + * Added `send_enc_key' and `enc_key_len' fields to the + SilcIDListData structure since they are needed in the re-key + phase. Affected file is silcd/idlist.[ch]. + + * Implemented the simple re-key protocol into the server. + Affected files silcd/server.c and silcd/protocol.[ch]. The + re-key will be performed once in an hour, by default. + + Added new protocol type SILC_PROTOCOL_SERVER_REKEY. + Added silc_server_rekey, silc_server_rekey_callback and + silc_server_rekey_final. + + * Removed Tunneled flag from the protocol. Updated the code + and the specifications. + + * Adde `pfs' field to the SilcIDListData to indicate whether + the PFS is to be performed in the re-key. Affected file is + silcd/idlist.h. + +Tue Apr 3 21:52:42 EEST 2001 Pekka Riikonen + + * Defined uint8, int8, uint16, int16, uint32, int32, uint64 and + int64 of at least the xintXX size. If void * is less that 4 + bytes uint32 * will be used. Defined bool as boolean. + + * Changed _ALL_ unsigned long and unsigned int to uint32, + unsgined short to uint16 in the source tree. + + * Fixed a fatal bug in silc_server_remove_clients_by_server. Do + not handle clients that has entry->data.registered == FALSE. + They are not in the network anymore. Affected file is + silcd/server.c. + +Tue Apr 3 16:39:19 EEST 2001 Pekka Riikonen + + * Implemented the sending of the SERVER_SIGNOFF notify in the + server. Affected file is silcd/server.c. + + * Added silc_server_send_notify_args into silcd/packet_send.[ch]. + Added also silc_notify_payload_encode_args into the + lib/silccore/silcnotify.[ch]. + + * Implemented ther SERVER_SIGNOFF notify handling in the server. + Affected file silcd/packet_receive.c. + + * Implemented the SERVER_SIGNOFF notify handling in the client + library. Affected file lib/silcclient/client_notify.c. Also, + implemnted the printing of the SERVER_SIGNOFF info to the + application. Affected file silc/client_ops.c. + + * The silc_idlist_del_server now returns TRUE or FALSE to indicate + if the deleting was successful. Affected file silcd/idlist.[ch]. + + * Added support for public key authentication in the connection + authentication protocol in the client library. Affected file + lib/silcclient/protocol.c. + + * Changed the server's silc_idlist_get_clients_by_* interface + to support already allocated array so that new entries may be + added to pre-allocated array. Affected file silcd/idlist.[ch]. + This fixes some bugs with WHOIS, WHOWAS and IDENTIFY commands + and command replies. + + * All command reply functions in the server now calls the + pending command callback even if error occured. This way the + error will be delivered to the client as well. Affected files + silcd/command.c and silcd/command_reply.c. + + * Fixed INFO command to return local server's info if no server + was provided. Affected file lib/silcclient/command.c. + + * Removed RESTART command for good. Updated the code and the + protocol specs. + + * Rewrote parts of the task system. It is a bit simpler now. + Removed unsued task priorities. The affected files are + lib/silcutil/silctask.[ch]. + +Mon Apr 2 20:02:33 EEST 2001 Pekka Riikonen + + * Moved the USERS printing from the library to the application. + Affected files lib/silcclient/command.c and silc/client_ops.c. + +Mon Apr 2 13:13:23 EEST 2001 Pekka Riikonen + + * Updated TODO. + + * Added channel key re-key support. The re-key is perfomed + only by the router and is done once in an hour. Added `rekey' + field to the SilcChannelEntry in the server. Affected files + silcd/server.c and silcd/idlist.h. + + * Added silc_task_unregister_by_context into the file + lib/silcutil/silctask.[ch]. + +Sun Apr 1 19:49:34 EEST 2001 Pekka Riikonen + + * Added SILC_UMODE_GONE mode to indicate when the client is not + present in the SILC network. Added also support to the local + command AWAY that will set this mode. Added support of showing + "xxx is gone" in WHOIS command. The USERS command shows the + gone status as well. + + * Fixed setting server and router operator privileges in the + server's UMODE command. Affected file silcd/command.c. + + * Merged the SKE KE1 and KE2 payloads into one payload. The + new KE payload is equivalent to the old KE2 payload. + + Cleaned up the SKE Start Payload parsing. It now uses the + simple buffer unformatting to do the parsing. A lot faster + now. + + Added new Mutual Authentication flag (SILC_SKE_SP_FLAG_MUTUAL) + to the SKE that is used to indicate whether both of the SKE + parties should perform authentication. By default only the + responder performs authentication. By setting this flag also + the initiator must do authentication. By default it is unset + since in normal SKE case, client to server connection, only + the responder should do authentication. When doing SKE between + two clients both should perform authentication. Updated the + code and the protocol specs. + + * A little fix to IDENTIFY command in the server. Search the + client first by hash not nickname. Affected file is + silcd/command.c. + + * Fixed the silc_client_close_connection to support closing + the client to client connections wihtout deleting too much + data. Affected file lib/silcclient/client.c. + + * Fixed a fatal bug in server and client; if KE1 or KE2 packets + are received if protocol used to be active but is not anymore + the application would crash due to NULL pointer dereference. + Affected files silcd/server.c and lib/silcclient/client.c. + + * Added `hash' field to the SilcClientConnection to include + the hash function negotiated in the SKE protocol. + + * Added new channel mode SILC_CMODE_FOUNDER_AUTH that is used + to set the channel founder authentication data. A client can + claim the founder rights later by providing the authentication + data to the CUMODE command using SILC_CUMODE_FOUNDER mode. + This way the channel founder can regain the channel founder + privileges even it is left the channel. This works only on + local server and the client must be connected to the same + server to be able to regain the founder rights. Updated the + protocol specs accordingly. + + Added support to the CMODE command in the client to set the + founder auth data. Read the README to see how to set it. + + Added support to the CUMODE command to claim the founder + rights. Read the README to see how to do it. + + Added support for the founder authentication to the Channel + Entry in the server. Affected file silcd/idlist.h. + + Added support for the SILC_CMODE_FOUNDER_AUTH mode in the + server's CMODE command. Affected file silcd/command.c. + + * Added the following new functions into lib/silccore/silcauth.[ch]: + silc_auth_get_method and silc_auth_get_data. + + * The server now saves the remote hosts public key to the + SilcIDListData pointer. Affected file silcd/protocol.c. + + * The normal server now does not remove the channel entry from + the cache if the founder authentication data is set. It used + to remove it if the founder was the last one on the channel on + the server and left the channel. The auth data is saved and + if the channel is re-joined later the old entry is used with + the old auth data. Affected files silcd/command_reply.c and + silcd/server.c. + + * Removed the `pkcs' field from the SilcIDListData structure + in the server; it is not used. Affected file silcd/idlist.h. + +Sat Mar 31 15:38:36 EEST 2001 Pekka Riikonen + + * Fixed packet processing on slow links. Partial packets were + never re-processed because the incoming data buffer was cleared + by the application. Application must not directly clear the + sock->inbuf, the packet processing routines handle it. Fixed + this in client library and in server. + +Fri Mar 30 16:35:27 EEST 2001 Pekka Riikonen + + * Fixed the WHOIS and IDENTIFY send reply function to really + check whether to send list or just one entry. Affected file + silcd/command.c. + + * Cleaned up the LEAVE command's channel key distribution. The + affected file silcd/command.c. + + * Changed CMODE_CHANGE's to as server + can enforce the channel mode as well. In that case the ID + includes the ID of the server. The code now enforces the + mode change if the router have different mode than the server. + + * The notify client operation with CMODE_CHANGE notify can now + return NULL client_entry pointer if the CMODE was not changed + by client. Application must check for this. + + * Added argument to INFO command to support server + info fetching by Server ID. + + * Added silc_server_announce_get_channel_users to get assembled + packets of channel users of the specified channel. Affected + file silcd/server.[ch]. + + * Fixed bug in CHANNEL_CHANGE notify in the server. The new ID + was freed underneath the ID Cache. + + * Re-announce clients when the server received CHANNEL_CHANGE + notify from the router. Affected file silcd/packet_send.c. + +Thu Mar 29 19:10:28 EEST 2001 Pekka Riikonen + + * Fixed a fatal bug when client does /join 1 2 3 4 5 6 the server + crashed since it did not handle the fact that there is no cipher + called "3" and didn't check the error condition. Now fixed. + + * Added SILC_MESSAGE_FLAG_REQUEST message flag as generic request + flag. It can be used to send message requests. + +Thu Mar 29 12:26:25 EEST 2001 Pekka Riikonen + + * Implemented the RESTART command in the client. + + * Added SILC_MESSAGE_FLAG_NOTICE message flag for informational + notice type messages. Added notice printing to the user + interface. + + * The channel keys are not re-generated if the channel's mode + is PRIVKEY, ie private key on the channel exists. Affected + files silcd/server.c and silcd/command.c. + + * Fixed a little bug in channel message delivery when channel + private keys are set in the server. Affected file is + silcd/packet_send.c. + + * Changed the setting on channel->on_channel = TRUE from the + silc_client_save_channel_key to the JOIN command reply. The + key payload is not received if the private channel key is set. + Affected file lib/silcclient/command_reply.c and the + lib/silcclient/client_channel.c. + + * When the CMODE_CHANGE notify is sent and the channel private + key mode is removed the channel key must be re-generated in + other cells as well. Added this support for the router in the + silcd/packet_receive.c. + + * Added new local command NOTICE to send notice message on + channel. Affected file silc/local_command.[ch]. + +Wed Mar 28 23:55:54 EEST 2001 Pekka Riikonen + + * Added new local command ME to the client. It is used to send + message to a channel with SILC_MESSAGE_FLAG_ACTION to indicate + some action. Affected file silc/local_command.[ch]. + + * Changed channel_message and private_message client operations + to deliver the message flags to the application. Added also + the `flags' arguments to the silc_client_send_channel_message + and silc_client_send_private_message functions. Affected file + silcapi.h. + +Wed Mar 28 20:50:47 EEST 2001 Pekka Riikonen + + * Redefined the Private Message Payload to support private message + keys and to support the new private message flags. Updated + the protocol specs. Flags makes it possible to have for example + CTCP style messages. + + * Added new type SilcPrivateMessagePayload and defined an API + for it in the lib/silcclient/silcprivate.[ch]. + + * Tested private message private keys successfully. Tested the + private message key set, unset and list commands with the new + KEY command. + + * Redefined the Channel Message Payload to include the channel + message flags (equal with private message flags) to support + for example CTCP style messages. + + * Defined some of the message (for channel and private message) + flags. Updated the protocol specs and added the flags to the + lib/silccore/silcchannel.h. The type is SilcMessageFlags. + +Wed Mar 28 15:52:36 EEST 2001 Pekka Riikonen + + * Added SilcKeyAgreementStatus type to the key agreement routines + to indicate the current status and error if one occured. + The status types are defined in the lib/silcclient/silcapi.h. + + * Added new local command KEY that is used to set and unset private + keys for channels, set and unset private keys for private messages + with remote clients and to send key agreement requests and + negotiate the key agreement protocol with remote client. The + key agreement is supported only to negotiate private message keys, + it currently cannot be used to negotiate private keys for channels, + as it is not convenient for that purpose. + + * Fixed a minor pending callback setting bug in the function + silc_client_get_client_by_id_resolve, now the function works. + Affected file lib/silcclient/idlist.c. + + * Added function silc_net_get_local_port to get local bound + port by socket. Added to lib/silcutil/silcnet.[ch]. + + * Added `sockets' and `sockets_count' fields to the SilcClient + object. They hold the sockets of the listenning sockets in + the client. Listenning sockets may be for example the key + agreement server. Affected file lib/silcclient/client.[ch]. + Added functions the silc_client_add_socket and the + silc_client_del_socket. They are exported to the application + as well. + + * Added ~./silc/clientkeys to support other client's public keys. + + * Renamed verify_server_key client operation to verify_public_key + and added one argument to indicate the type of the connection + (server, client etc.). + +Tue Mar 27 22:22:38 EEST 2001 Pekka Riikonen + + * Added silc_server_connection_auth_request to handle the + incoming CONNECTION_AUTH_REQUEST packet. Affected file is + silcd/packet_receive.[ch]. + + * Added silc_server_send_connection_auth_request into the + silcd/packet_send.c to send the connection auth request packet. + + * Cleaned up the silcd/protocol.c a bit and fixed some memory + leaks. + + * Fixed the public key authentication in responder side in the + server. The `auth_data' pointer includes the SilcPublicKey + not the path to the public key. Affected file silcd/protocol.c. + + * Implemented the public key authentication in the initiator side + in the server. Affected file silcd/protocol.c. + + * Removed the [RedirectClient] config section from the server + configuration. Is not needed and I don't want to implement it. + +Tue Mar 27 12:49:56 EEST 2001 Pekka Riikonen + + * Cleaned up the CMODE command in the server. It now works + correctly and supports all the modes defined in the protocol. + Affected file is silcd/command.c. + + * Added `hmac_name' field to the SilcChannelEntry in the server + to hold the default HMAC of the channel. It can be set when + creating the channel (with JOIN command). Affected files + silcd/idlist.[ch]. + + * Added and argument to the CMODE_CHANGE notify + type to indicate the change of the current cipher and hmac + on the channel. Client can safely ignore the argument + (if it chooses to do so) since the CHANNEL_KEY packet will + force the channel key change anyway. The argument is + important since the client is responsible of setting the new + HMAC and the hmac key into use. + + * Fixed the CMODE command in the client library as well. + + * Tested CMODE command in router environment successfully. + +Mon Mar 26 14:39:48 EEST 2001 Pekka Riikonen + + * Show the version of the remote client (or server) when connecting + to the server. It is logged to the log file. Affected file + is silcd/protocol.c. + + * Fixed the KILLED notify handling in the client library. The + client must be removed from all channels when receiving the + KILLED notify. + + Also, do not remove the client entry when giving the KILL + command but when the KILLED notify is received. + + * Removed silc_idlist_find_client_by_nickname from the server. + Not needed anymore. Affected files silcd/idlist.[ch]. + + * Implemented the CHANNEL_CHANGE notify type handling to the + server. Affected file silcd/server.c. + + * Updated TODO. + +Mon Mar 26 12:11:14 EEST 2001 Pekka Riikonen + + * Added silc_server_send_notify_invite to send the INVITE + notify between routers. + + * Implemented the INVITE command correctly to the server. + + * Implemented the INVITE notify type handling in the server. + + * Implemented the INVITE command to the client library and on the + user interface. + +Sun Mar 25 20:27:09 EEST 2001 Pekka Riikonen + + * Added function silc_server_get_client_resolve to find the + client entry by ID from all ID lists and then resolve it + (using WHOIS) if it cannot be found. Affected file is + silcd/server.[ch]. + +Sun Mar 25 13:52:51 EEST 2001 Pekka Riikonen + + * Implemented the BAN command to the client library. + + * The JOIN command in the server now checks the invite list + and the ban list. + + * Changed the silc_command_reply_payload_encode_va and the + silc_command_payload_encode_va to support that if argument is + NULL it ignores and checks the next argument. Affected file + lib/silccore/silccommand.c. + + * Added silc_server_send_notify_ban to send the BAN notify + type between routers. + + * Chaned the silc_notify_payload_encode to support that if + argument is NULL it ignores and checks the next argument. + Affected file lib/silccore/silcnotify.c. + + * Tested ban lists in router environment successfully. + +Sat Mar 24 14:47:25 EET 2001 Pekka Riikonen + + * Implemented BAN command to the server, in silcd/command.[ch]. + + * Removed the BAN and INVITE_LIST modes from the CMODE command + in the server code. + + * Added function silc_string_match to regex match two strings. + Affected files lib/silcutil/silcutil.[ch]. + +Fri Mar 23 22:02:40 EET 2001 Pekka Riikonen + + * Redefined parts of the SilcChannelEntry in the server to support + the new ban and invite lists. + +Fri Mar 23 16:25:11 EET 2001 Pekka Riikonen + + * Redefined the INVITE command. The same command can be used to + invite individuals to the channel but also to manage the invite + list of the channel (to add to and remove from the invite list). + Updated the protocol specs. + + * Added new command SILC_COMMAND_BAN that can be used to manage + the ban list of the channel. Updated the protocol specs. + + * Removed the channel modes: the SILC_CMODE_BAN and the + SILC_CMODE_INVITE_LIST as they were a bit kludge to be included + in the CMODE command. The equivalent features are now available + using INVITE and BAN commands. Updated the protocol specs. + + * Added new SILC_NOTIFY_TYPE_BAN notify type to notify routers + in the network about change in the current ban list. The notify + type is not used by the client. + + * Redefined parts of the SILC_NOTIFY_TYPE_INVITE command to + support the invite lists. + +Thu Mar 22 22:52:23 EET 2001 Pekka Riikonen + + * Added new function silc_string_regexify that converts string + including wildcard characters into regex string that can + be used by the GNU regex library. Added into the file + lib/silcutil/silcutil.[ch]. + + Added silc_string_regex_combine to combine to regex strings + into one so that they can be used as one regex string by + the GNU regex library. Added into the file + lib/silcutil/silcutil.[ch]. + + Added silc_string_regex_match to match two strings. It returns + TRUE if the strings match. Added into lib/silcutil/silcutil.[ch]. + +Thu Mar 22 15:29:42 EET 2001 Pekka Riikonen + + * Imported GNU regex to the soruce tree into lib/contrib. + Fixed some compiler warning from the regex.c. + +Wed Mar 21 15:27:58 EET 2001 Pekka Riikonen + + * Fixed MOTD command in the server to work in router environment. + + * Fixed the MOTD command in the client library to support + the server argument in the command. + + * Added `nickname_len' argument to the silc_idlist_add_client + in the server, as the `nickname' argument may be binary data + (it may be hash). + + * Added silc_idlist_get_channels to return all channels from + the ID list. + + * Implemented LIST command to the server. Affected file is + silcd/command.c. + + * Implemented the LIST command to the client library and on the + user interface. + + * Added [] argument to the LIST command reply. + With private channels the user count is not shown. + + * Updated TODO and README. + +Tue Mar 20 21:05:57 EET 2001 Pekka Riikonen + + * The client entry's data.registered must be TRUE even with + global client entry on global client list. The data.registered + is used to check whether the client is anymore in the network, + for example with WHOWAS command so it must be valid. + + * Fixed the WHOWAS command in the server. It now actually works + in router environment. Added function into silcd/command_reply.c + silc_server_command_reply_whowas_save. + + * Added silc_idlist_purge function to the silcd/idlist.c + to periodically purge the ID Cache. + + * Fixed INFO command in the server. It works now in router + environment. Added argument to the INFO command + reply. Updated the protocol specs. + + * Fixed minor bug in silc_idcache_purge to not purge if the + expire value is zero. + + * Fixed various bugs in WHOIS and IDENTIFY command handling as + they were buggy because of the WHOWAS information. + + * Fixed local command MSG to handle the async resolving of + the remote client properly. It used to fail the first MSG. + Affected file silc/local_command.c. + + * Added `data_len' field to SilcIDCache context. + +Tue Mar 20 16:29:00 EET 2001 Pekka Riikonen + + * Update TODO. Todo in commands in the server. + +Tue Mar 20 15:45:14 EET 2001 Pekka Riikonen + + * Added new notify type SILC_NOTIFY_TYPE_UMODE_CHANGE that is + used by routers as broadcast packet to inform other routers + about the changed user mode. + + Implemented the notify handling in the server. Affected file is + silcd/packet_receive.c. Added the function + silc_server_send_notify_umode to the silcd/packet_send.[ch]. + + * Added new generic Channel Payload and deprecated the New Channel + Payload. The New Channel Payload is now the generic Channel + Payload. + + * Added new argument `mode' to the silc_server_send_new_channel + as it is required in the Channel Payload now. + + * Renamed the SilcChannelPayload to SilcChannelMessagePayload + and created a new and real SilChannelPayload to represent the + new generic Channel Payload. Implemented the encode/decode + for Channel Payload. Affected file lib/silccore/silcchannel.[ch]. + + * Added silc_server_get_client_channel_list to return the list + of channels the client has joined for WHOIS command reply. + Affected file silcd/server.[ch]. + + * Implemented the channel list sending in the WHOIS command reply + in server and in the client. + + Implemented the channel list displaying on the user interface + as well. Affected file silc/client_ops.c. + + * Added silc_channel_payload_parse_list to parse list of Channel + Payloads. It returns SilcDList list of SilcChannelPayloads. + Client for example can use this function to parse the list of + channels it receives in the WHOIS command reply. The caller + must free the list by calling silc_channel_payload_list_free. + Affected files lib/silccore/silcchannel.[ch]. + +Mon Mar 19 21:39:15 EET 2001 Pekka Riikonen + + * Added one new argument to the WHOIS command reply + to return the mode of the user in SILC. Updated the protocol + specs. + + Implemented it to the server and client. + +Mon Mar 19 18:43:06 EET 2001 Pekka Riikonen + + * Fixed the mode printing on the user interface on joining. + Affected file silc/client_ops.c. + + * Implemented the UMODE command and user modes in general to the + client library and to the user interface. + + * Implemented the UMODE command to the server. + + * The server now sends UNKNOWN_COMMAND error status if client sends + unknown command. Affected file silcd/command.c. + + * All server commands now handle the command identifier the right + way when sending the command reply to the client. The client can + use to identify the command replies with the identifier. + +Mon Mar 19 16:13:07 EET 2001 Pekka Riikonen + + * Added silc_server_get_client_route to resolve the route to + the client indicated by the client ID. Affected file is + silcd/server.[ch]. + + * Added silc_server_relay_packet as general function to relay + packet to arbitrary destination. This deprecates functions + like _send_private_message_key, _relay_notify etc. Affected + file is silcd/packet_send.[ch]. + + Removed silc_server_send_key_agreement, + silc_server_send_private_message_key and + silc_server_packet_relay_notify functions from the file + silcd/packet_send.[ch]. + + * Updated TODO. + + * Implemented the SILC_NOTIFY_TYPE_KILLED notify handling in the + server. Affected file silcd/packet_receive.[ch]. + + * Implemented the KILL command to the client. Implemented the + SILC_NOTIFY_TYPE_KILLED notify handling in the client library. + Affected files lib/silcclient/command[_reply].c and + lib/silcclient/client_notify.c. Implemented the KILL notify + printing in the user inteface. + + * Fixed a lot silc_parse_nick memory leaks from the client + library in the file lib/silcclient/command.c. + + * Changed the silc_server_send_notify_on_channels's `sender' + argument from SilcSocketConnection to SilcClientEntry to + check the sender as entry and not as connection object and not + to send to the client provided as argument. The affected file + is silcd/packet_send.[ch]. + + * The notify packets that are destined directly to the client used + to not to be processed by the server. Now changed that and the + server processes all notify packets. After relaying the packet + to the client the notify packet is processed in the server. + + * The silc_server_free_client_data now checks whether there is + pending outgoing traffic for the client and purges the data to + the network before removing the client entry. + +Sun Mar 18 21:02:47 EET 2001 Pekka Riikonen + + * Added SILC_NOTIFY_TYPE_KILLED notify type. It is sent when + an client is killed from the SILC Network. Updated the protocol + specs accordingly. + + Added new function silc_server_send_notify_killed to the + silcd/packet_send.[ch]. + + * Added function silc_server_packet_relay_notify to relay notify + packets that are destined directly to a client. In this case + the server does not process the notify packets but merely relays + it to the client. Affected file silcd/packet_send.[ch]. + + Added also silc_server_packet_process_relay_notify to check + whereto relay the notify. Affected file is + silcd/packet_receive.[ch]. + + * Implemented the KILL command to the server. + + * Updated TODO. + + * Added the backup schema desgined last fall to the protocol + specs for everyone to see. The specification is in the + *-spec-xx.txt draft and the packet type definitions for the + backup routers is in *-pp-xx.txt draft. Thusly, added also + new packet type SILC_PACKET_CELL_ROUTERS. + + * A big security problem in the implementation discovered. The + signoff of an client did not cause new channel key generation + which it of course should've done. The channel keys must be + always re-generated when client leaves (or signoffs) the channel. + The silc_server_remove_from_channels funtion now handles + the channel key re-generation. + + * Added `sender' argument to the silc_server_send_notify_on_channels + to not to send the client provided as argument. Affected file + silcd/packet_send.[ch]. + +Fri Mar 16 15:52:49 EET 2001 Pekka Riikonen + + * Implemented OPER and SILCOPER commands into the server and + the client library. + + * Added silc_auth_verify and silc_auth_verify_data to verify + the authentication directly from the authentication payload. + It supports verifying both passphrase and public key based + authentication. Affected file lib/silccore/silcauth.[ch]. + + * Added `hash' field to the SilcIDListData structure. It is the + hash negotiated in the SKE protocol. Affected file is + silcd/idlist.[ch]. + + * Slight redesigning of the SilcAuthPayload handling routines. + Do not send SilcPKCS but SilcPublicKey as argument. + + * Implemented the public key authentication support to the + serverconfig. The public key is loaded from the provided path + and saved as authentication data to void * pointer. Thus, + changed the unsigned char *auth_data to void *auth_data; + + * Fixed SHUTDOWN command to send the reply before the server + is shutdown. :) Affected file silcd/command.c. + + * Fixed fatal bug in CONNECT command. The hostname was invalid + memory and server crashed. Affected file silcd/command.c. + + * Fixed fatal bug in CLOSE command. The server_entry became + invalid but was referenced later in the command. Affected file + silcd/command.c. + +Thu Mar 15 12:46:58 EET 2001 Pekka Riikonen + + * Fixed fatal bug in failure packet handling. Server ignored + the failure and thus crashed when it came. + + * Updated TODO. + +Wed Mar 14 20:37:35 EET 2001 Pekka Riikonen + + * Added new SILC_CF_LAG_STRICT command flag that strictly forces + that the command may be executed only once in (about) 2 seconds. + The old SILC_CF_LAG flag is same but allows command bursts up + to five before limiting. + + Added the support for CF_LAG and CF_LAG_STRICT flags to the + server code. Various commands now includes the CF_LAG_STRICT + flag to disallow any kind of miss-use of the command. + + * Fixed the silc_buffer_unformat to not to allocate any data + if the length of the data is zero. It used to allocate the + length + 1. Affected file lib/silcutil/silcbuffmt.c. + +Wed Mar 14 16:10:30 EET 2001 Pekka Riikonen + + * Changed the format of AdminConnection configuration section + in the server. Added username of the admin to the format. + Affected files silcd/serverconfig.[ch]. + + Added silc_server_config_find_admin into silcd/serverconfig.[ch] + to return admin configuration data by host, username and/or + nickname. + +Wed Mar 14 13:18:16 EET 2001 Pekka Riikonen + + * Implemented WHOWAS command to the server. Added the functions: + + silc_server_command_whowas_parse, + silc_server_command_whowas_send_reply, + silc_server_command_whowas_from_client and + silc_server_command_whowas_from_server + + * Added argument to the WHOWAS command reply. Updated + the protocol specs accordingly. + + * Implemented WHOWAS command and command_reply to the client + library. + + Implemented the WHOWAS printing on the user interface. + +Tue Mar 13 22:17:34 EET 2001 Pekka Riikonen + + * Added new argument to the WHOWAS command reply, the real name. + It is an optional argument. Updated the protocol specs. + + * Added SilcIDCacheDestructor callback that is registered when + the SilcIDCache is allocated. The callback is called when + an cache entry in the ID Cache expires, or is purged from the + cache. Added into lib/silccore/idcache.[ch]. + + Added silc_idlist_client_destructor to the silcd/idlist.[ch] + to destruct the client entries when the cache entry expires. + Other ID Cache's in server and in the client library ignores + the destructor. + + * If the ID Cache entry's `expire' field is zero then the entry + never expires. Added boolean `expire' argument to the + silc_idcache_add function in the lib/silccore/idcache.[ch]. + If it is TRUE the default expiry value is used. + + * Added silc_server_free_client_data_timeout that is registered + when client disconnects. By default for 5 minutes we preserve + the client entry for history - for WHOWAS command. + +Tue Mar 13 13:26:18 EET 2001 Pekka Riikonen + + * Added support to the server to enforce that commands are not + executed more than once in 2 seconds. If server receives + commands from client more frequently, timeout is registered + to process the commands. Affected file silcd/command.c. + Added new function silc_server_command_process_timeout. + + * Changed NICK_NOTIFY handling in client library to check that + if the client's nickname was changed, so there is no need to + resolve anything from the server. + + * Removed error printing from the WHOIS and IDENTIFY commands. + If error occurs then it is ignored silently in the client library. + The application, however, may map the received error to + human readable error string. The application currently maps + the NO_SUCH_NICKNAME error to string. + + * Made the command status message public to the application. Moved + them from lib/silcclient/command_reply.c to + lib/silcclient/command_reply.h. The application can map the + received command status to the string with the + silc_client_command_status_message function. + + * Added check to the server to check that client's ID is same + as the Source ID in the packet the client sent. They must + match. + +Tue Mar 13 12:49:21 EET 2001 Pekka Riikonen + + * Added dist-bzip hook to the Makefile.am to make bzip2 + compressed distributions. + +Mon Mar 12 18:43:38 EET 2001 Pekka Riikonen + + * Server now enforces the maximum length for the nickname and + the channel as protocol specification dictates. 128 bytes for + nickname and 256 bytes for channel name. + + * Moved the WHOIS printing to the application. The client libary + does not print out the WHOIS information anymore, the application + must do it. Renamed silc_client_command_reply_whois_print to + the silc_client_command_reply_whois_save. + + The client's idle time is also sent to the application now, and + the idle is shown on screen. + + * Added silc_client_command_reply_identify_save to save the + received IDENTIFY entries. + + * Do not check for channel private keys in message sending and + reception if the channel does not have the PRIVKEY mode set. + Affected file lib/silclient/client_channel.c. + +Sun Mar 11 20:25:06 EET 2001 Pekka Riikonen + + * Fixed a minor bug if WHOIS and IDENTIFY command parsing that + just surfaced after chaning the JOIN procedure. + +Sun Mar 11 14:59:05 EET 2001 Pekka Riikonen + + * Added silc_client_get_clients_by_list to get client entries + from Client ID list, that is returned for example by JOIN + and USERS command replies. The application should use this + function for example when JOIN command reply is received to + resolve the clients already on the channel (library does not + do that anymore as USERS command reply is not used in the JOIN + procedure anymore). Affected files lib/silcclient/silcapi.h and + lib/silcclient/idlist.c. + + * JOIN command reply and USERS command reply returns now SilcBuffer + pointers instead of unsigned char pointers when returning + the client list and mode list. + + * Added argument to the JOIN command reply, mainly + for the server to identify for which client the command was + originally sent. Updated protocol specs accordingly. + + * Added SilcDlist private_key pointer to the SilcChannelEntry + in the client to support the channel private keys. Affected + file is lib/silcclient/idlist.h. + + * Added SilcChannelPrivateKey argument to the function + silc_client_send_channel_message so that application can choose + to use specific private ke if it wants to. If it is not provided, + the normal channel key is used, unless private keys are set. + In this case the first (key that was added first) is used + as the encryption key. + + * Implemented the support for channel private key handling. + Implemented the following functions: + + silc_client_add_channel_private_key, + silc_client_del_channel_private_keys, + silc_client_del_channel_private_key, + silc_client_list_channel_private_keys and + silc_client_free_channel_private_keys + + Affected file lib/silcclient/client_channel.c. + + * Added the support for the private keys in the channel message + sending and encryption and in the message reception and + decryption. Affected funtions are + silc_client_send_channel_message and silc_client_channel_message. + +Sat Mar 10 21:36:22 EET 2001 Pekka Riikonen + + * Added SKE's key verify callback to the client library's + KE protocol context. Affected files lib/silcclient/protocol.[ch]. + + * Removed the statement that server (or router) must send USERS + command reply when joining to the channel so that the client + knows who are on the channel. Instead, the client list and + client's mode list is now sent in the JOIN command reply to the + client who joined channel. This is better solution. + + * Added function silc_server_get_users_on_channel and function + silc_server_save_users_on_channel to the silcd/server.[ch]. + + * Removed function silc_server_command_send_users from the + silcd/command.c. + + * Do not show topic on the client library anymore. The topic is + sent in the command reply notify to the application and the + application must show the topic now. + +Sat Mar 10 00:07:37 EET 2001 Pekka Riikonen + + * Added client searching by nickname hash into the IDENTIFY and + WHOIS commands in the server as they were clearly missing from + them. Affected file is silcd/command.c. + + * Fixed a bug in private message receiving in the client library. + The remote ID was freed and it wasn't supposed, now it is + duplicated. + +Fri Mar 9 12:40:42 EET 2001 Pekka Riikonen + + * Minor fix to the channel payload; allocate the data area, as it + needs to be of specific length. + + * If the key agreement port is zero then the operating + system will define the bound port. Affected files are + lib/silcclient/silcapi.h and lib/silcclient/client_keyagr.c. + + * Added new function silc_channel_payload_decrypt into the file + lib/silccore/silcchannel.[ch]. + + * Moved the channel message etc, check from silc_packet_decrypt + to applications. The library calls now a generic + SilcPacketCheckDecrypt callback which is to return TRUE or FALSE + when the packet is either normal or special. This was done to + allow more wide range of checking that was not allowed when + the code was in library. Now applications can do virtually any + checks to the packet and return to the library the decision how + the packet should be processed. Affected files are + lib/silccore/silcpacket.[ch]. + + Added silc_server_packet_decrypt_check to the server and + silc_client_packet_decrypt_check to the client library. + + * Added silc_server_packet_send_srcdest into silcd/packet_send.[ch] + to send with specified source and destination information. + + * Channel message delivery between routers was broken after the + channel key distribution was fixed earlier. The channel key + was used be to distributed to other routers as well which is not + allowed by the protocol. Now this is fixed and channel keys + really are cell specific and the channel message delivery between + routers comply with the protocol specification. + + * Fixed various commands in server to check also the global list + for the channel entry and not just the local list. The affected + file silcd/command.c. + +Thu Mar 8 21:39:03 EET 2001 Pekka Riikonen + + * Added assert()s to buffer formatting and unformatting routines + to assert (if --enable-debug) when error occurs. Affected + file: lib/silcutil/silcbuffmt.c. + + * Changed to auto-reconnect to check whether the remote host is + router and register the re-connect timeout if it is. It used + to check that whether we are normal server, but router must do + auto-reconnect with another router as well. Affected file + silcd/server.c. + + * Removed the [] option from CMODE command as the cipher + name decides the key length, nowadays. See the defined ciphers + from the protocol specification. + + * Added [] option to the CMODE command to define the HMAC + for the channel. Added SILC_CMODE_HMAC channel mode. + + * Added [] option for the JOIN command so that user can + select which HMAC is used to compute the MACs of the channel + messages. + + * Added Hmac field to the Channel Message Payload. The integrity + of plaintext channel messages are now protected by computing + MAC of the message and attaching the MAC to the payload. The + MAC is encrypted. Now, it is clear that this causes some + overhead to the size of the packet but rationale for this is that + now the receiver can verify whether the channel message decrypted + correctly and also when private keys are set for the channel the + receiver can decrypt the packet with several keys and check from + the MAC which key decrypted the message correctly. + + * Added silc_cipher_encrypt and silc_cipher_decrypt into the + lib/silccrypt/silccipher.[ch]. + + * Added silc_hash_len to return the digest length into the + lib/silcrypt/silchash.[ch]. + + * Rewrote parts of Silc Channel Payload interface in the + lib/silccore/silcchannel.[ch]. The encode function now also + encrypts the packet and parse function decrypts it. + +Wed Mar 7 20:58:50 EET 2001 Pekka Riikonen + + * Fixed a minor formatting bug in the SKE's key material processing. + It actually might have processed the keys wrong way resulting + into wrong keys. + + * Redefined the mandatory HMAC algorithms and added new algorithms. + Added hmac-sha1-96 and hmac-md5-96 which are normal hmac-sha1 + and hmac-md5 truncated to 96 bits. The mandatory is now + hmac-sha1-96. Rest are optional (including the one that used + to be mandatory). Rationale for this is that the truncated HMAC + length is sufficient from security point of view and can actually + make the attack against the HMAC harder. Also, the truncated + HMAC causes less overhead to the packets. See the RFC2104 for + more information. + + * Added new [hmac] configuration section. The SKE used to use + the hash names (md5 and sha1) in the SKE proposal as HMCAS which + is of course wrong. The official names that must be proposed in + the SKE are the ones defined in the protocol specification + (hmac-sha1-96 for example). The user can configure any hmac + using any hash function configured in the [hash] section. At + least, the mandatory must be configured. + + Rewrote the HMAC interface in lib/silccrypt/silchmac.[ch]. + + * Added HMAC list to the SKE proposal list. It has now both + hash algorithm list and HMAC list. This makes the protocol + incompatible with previous versions. The SKE now seems to work + the way it is supposed to work, for the first time actually. + + * Defined plain Hash algorithms to the protocol specification. + Added sha1 and md5. + +Tue Mar 6 15:36:11 EET 2001 Pekka Riikonen + + * Implemented support for key agreement packets into the server. + Added functions silc_server_key_agreement and + silc_server_send_key_agreement. Other than these functions, + server has nothing to do with this packet. + + * Added support for private message key packets into the server. + Added functions silc_server_private_message_key and + silc_server_send_private_message_key. + + * Updated TODO. + + * Changed the silc_[client|server]_protocol_ke_set_keys to be + called in the protocol's final callback instead in the END + protocol state. This makes a little more sense and in the same + time in client we can use the same protocol routines for normal + key exchange and to key agreement packet handling as well. + + * Added to both client's and server's KE protocol context the + SilcSKEKeyMaterial pointer to save the key material. We will + bring the key material to the protocol's final callback by doing + this. The final callback must free the key material. + + * Added SKE's packet_send callback into client's KE protocol + context so that the caller can choose what packet sending function + is used. This way we can use different packet sending when + doing normal SKE when doing key agreement packet handling (in + the key agreement packet handling we do not want to encrypt + the packets). + + * Implemented the responder side of the key agreement routines + in the client. The client can now bind to specified port and + accept incoming key negotiation. The key material is passed + to the application after the protocol is over. + + * Implemented the processing of incoming Key Agreement packet + in the client. Added function silc_client_key_agreement to + process the packet. + + * Implemented the intiator side of the key agreement routines + in the client. The client can now initiate key agreement with + another remote client. The key material is passed to the + application after the protocol is over. + + * Created client_keyagr.c to include all the key agreement + routines. + + * Added macro SILC_TASK_CALLBACK_GLOBAL which is equal to the + SILC_TASK_CALLBACK except that it is not static. + + * Created client_notify.c and moved the Notify packet handling + from the client.[ch] into that file. + + * Created client_prvmsg.c and moved all private message and + private message key routines from the client.[ch] into that file. + + * Create client_channel.c and moved all channel message and + channel private key routines from the client.[ch] into that file. + + * Changed silc_client_get_client_by_id_resolve to resolve with + WHOIS command instead of IDENTIFY command, in the file + lib/silclient/idlist.c. + +Mon Mar 5 18:39:49 EET 2001 Pekka Riikonen + + * Implemented the SKE's responder side to the Client library. + + * When FAILURE is received to the protocol do not trust it + blindly. Register a timeout to wait whether the remote closes + the connection as it should do it, only after that process the + actual failure. This was changed to both client and server. + + * Added client_internal.h to include some of the structures + there instead of client.h in lib/silcclient/. + + * Added function silc_task_unregister_by_callback to unregister + timeouts by the callback function. + +Sat Mar 3 19:15:43 EET 2001 Pekka Riikonen + + * Some "Incomplete WHOIS info" errors has been appearing on the + log files. Took away the entry->userinfo check from WHOIS + reply sending. The entry->userinfo is now " " if client did not + provide one. I thought this was fixed earlier but something + is wrong still. Let's see if the error still appears. + +Wed Feb 28 20:56:29 EET 2001 Pekka Riikonen + + * Fixed a minor bug in the login when the channel key is + re-generated in the server. It used to generate the key in + wrong order and thus caused problems in the channel traffic. + + * Fixed a minor bug in channel key distsribution after + KICK command. The key was not sent to the router even though + it should've been. + +Tue Feb 27 20:24:25 EET 2001 Pekka Riikonen + + * Added silc_ske_process_key_material_data as generic routine + to process any key material as the SILC protocol dictates. The + function is used by the actual SKE library but can be used by + applications as well. This relates to the private message keys + and the channel private keys since they must be processed the + same way the normal SILC session keys. The protocol dictates + this. Affected files: lib/silcske/silcske.[ch]. + + Added also silc_ske_free_key_material to free the + SilcSKEKeyMaterial structure. + + * Defined silc_cipher_set_key function to set the key for + cipher without using the object's method function. The affected + files: lib/silccrypt/silccipher.[ch]. + + * Implemented silc silc_client_add_private_message_key, + silc_client_add_private_message_key_ske, + silc_client_del_private_message_key, + silc_client_list_private_message_keys and + silc_client_free_private_message_keys functions in the + client library. + + Added functions silc_client_send_private_message_key to send + the Private Message Key payload and silc_client_private_message_key + to handle incoming Private Message Key payload. + + * Added Cipher field to the Private Message Key payload to set + the cipher to be used. If ignored, the default cipher defined + in the SILC protocol (aes-256-cbc) is used. + +Tue Feb 27 13:30:52 EET 2001 Pekka Riikonen + + * Removed lib/silcclient/ops.h file. + + Redefined parts of the SILC Client Library API. Created new + file silcapi.h that deprecates the ops.h file and defines the + published Client Library API. Defined also private message key + API and channel private key API into the file. + + This is the file that the application must include from the + SILC Client Library. Other files need not be included by + the application anymore. + + * Added new key_agreement client operation callback and also + defined the Key Agreement library API for the application. + +Tue Feb 27 11:28:31 EET 2001 Pekka Riikonen + + * Added new packet type: SILC_PACKET_KEY_AGREEMENT. This packet + is used by clients to request key negotiation between another + client in the SILC network. If the negotiation is started it + is performed using the SKE protocol. The result of the + negotiation, the secret key material, can be used for example + as private message key. + + Implemented the Key Agreement payload into the files + lib/silccore/silauth.[ch]. + +Mon Feb 26 12:13:58 EET 2001 Pekka Riikonen + + * Redefined ciphers for the SILC protocol. Added some new ciphers + and defined the key lengths for the algorithms. Changed the + code accordingly. The default key length is now 256 bits. + + * Fixed SKE key distribution function silc_ske_process_key_material + when the key length is more than 128 bits. The default key + length in SILC is now 256 bits. + + * Added new command status type: SILC_STATUS_ERR_UNKOWN_ALGORITHM + to indicate unsupported algorithm. + + * Renamed rijndael.c to aes.c and all functions as well. + + * Fixed a long standing channel key setting bug in client library. + Weird that it has never surfaced before. + + * Fixed bug in channel deletion. If the entire channel is removed + then it must also delete the references of the channel entry + from the client's channel list as the client's channel entry and + the channel's client entry share same memory. + +Sun Feb 25 20:47:29 EET 2001 Pekka Riikonen + + * Implemented CONNECT and SHUTDOWN commands in the client. + + * Implemented CLOSE command to the client. + + * Added the function silc_idlist_find_server_by_name into the + files silcd/idlist.[ch]. + + Added the function silc_idlist_find_server_by_conn into the + files silcd/idlist.[ch]. + +Sat Feb 24 23:45:49 EET 2001 Pekka Riikonen + + * DIE command was renamed to SHUTDOWN. Updated the both code + and protocol specs. + + * Defined SILC_UMODE_NONE, SILC_UMODE_SERVER_OPERATOR and + SILC_UMODE_ROUTER_OPERATOR modes into lib/silccore/silcmode.h. + + * Implemented CONNECT, CLOSE and SHUTDOWN commands to the server + side. + + * Added function silc_server_create_connection function to create + connection to remote router. My server implementation actually + does not allow router to connect to normal server (it expects + that normal server always initiates the connection to the router) + so the CONNECT command is only good for connecting to another + router. + +Sat Feb 24 16:03:45 EET 2001 Pekka Riikonen + + * Added SILC_NOTIFY_TYPE_KICKED to indicate that the client + or some other client was kicked from the channel. + + Implemented the handling of the notify type to both client + and server. + + Implemented silc_server_send_notify_kicked to send the KICKED + notify. It is used to send it to the server's primary router. + + * Implemented the KICK command into server and client. + + * Added `query' argument to the silc_idlist_get_client function + to indicate whether to query the client from server or not if + it was not found. + + * Added new command status type SILC_STATUS_ERR_NO_CHANNEL_FOPRIV + to indicate that the client is not channel founder. + + * Updated TODO. + +Sat Feb 24 00:00:55 EET 2001 Pekka Riikonen + + * Removed the rng context from SilcPacketContext structure and + changed that the packet routine uses the Global RNG API. + +Fri Feb 23 11:22:57 EET 2001 Pekka Riikonen + + * Added support for quit message that client can "leave" on the + channel when it quits the SILC. It is ditributed inside the + SILC_NOTIFY_TYPE_SIGNOFF notify type. + + Added silc_server_free_client_data that will take the + signoff message as argument. + + * Changed SKE routines to use the silc_pkcs_sign/verify routines. + +Thu Feb 22 23:05:36 EET 2001 Pekka Riikonen + + * Updated parts of the protocol specification to keep it up + to date. + +Thu Feb 22 15:08:20 EET 2001 Pekka Riikonen + + * Added List flag (SILC_PACKET_FLAG_LIST) to indicate list of + payloads in one packet. + + * Deprecated following packet types: NEW_ID_LIST, NEW_CHANNEL_LIST, + NEW_CHANNEL_USER_LIST, SET_MODE and SET_MODE_LIST. List packets + use now the new List flag. + + * Also deprecated the following packet types: REPLACE_ID, + NEW_CHANNEL_USER and REMOVE_CHANNEL_USER packet types. + + * Added list support for Notify packet in server. + + * Added silc_server_send_notify_channel_change to send the + CHANNEL_CHANGE notify type to replace channel ID's. Deprecates + the silc_server_send_replace_id. + + * Added silc_server_send_notify_nick_change to send the + NICK_CHANGE notify type. Deprecates the function + silc_server_send_replace_id. + + * Added silc_server_send_notify_join to send the JOIN notify type. + Deprecates the function silc_server_send_new_channel_user. + + * Added silc_server_send_notify_leave to send LEAVE notify type. + Deprecates the function silc_server_send_remove_channel_user. + + * Added silc_server_send_notify_cmode and + silc_server_send_notify_cumode to send CMODE and CUMODE notify + types. Deprecates the silc_server_send_set_mode function. + + * Added SERVER_SIGNOFF notify type to indicate that server has + quit. This means that all clients on the channel from that + server will drop. This can be also used when netsplit happens. + + Deprecated REMOVE_ID packet type since it is not needed anymore + even from server. + + Added silc_server_send_notify_server_signoff to send the + SERVER_SIGNOFF notify type. Deprecates the function + silc_server_send_remove_id. + + Added also silc_server_send_notify_signoff to send the + SIGNOFF notify type. + + * Employed the PKCS #1. It is the mandatory way to do RSA in the + SILC protocol from this day on. Changed the protocol + specification as well. + + * Added silc_server_send_notify_topic_set to send TOPIC_SET + notify type. It is used between routers to notify about + topic changes on a channel. + + * Added silc_id_dup into lib/silccore/id.[ch] to duplicate + ID data. + + * Partly updated the protocol specification to comply with the + changes now made. It is still though a bit outdated. + + * The JOIN notify type now takes one extra argument . + The packet used to be destined to the channel but now the + JOIN type may be sent as list thus it is impossible to + destine it to any specific channel. By adding this argument + it is again possible. + +Wed Feb 21 22:39:30 EET 2001 Pekka Riikonen + + * Added CREDITS file. The CHANGES and CREDITS file will appear + in the distribution as well. + +Wed Feb 21 14:17:04 EET 2001 Pekka Riikonen + + * Implemented CMODE_CHANGE, CUMODE_CHANGE and TOPIC_SET notify + types in the server's silcd/packet_receive.c. + + * Implemented CMODE and CUMODE to work in router environment. + + * Fixed minor encoding and decoding buglet from the + lib/silccore/silcmode.c. + + * Fixed buffer overflow from lib/silcclient/command.c in USERS + command parsing. + +Wed Feb 21 12:44:00 EET 2001 Mika Boström + + * Changed all SilcConfigServer* and silc_config_server* to + SilcServerConfig* and silc_server_config*, respectively. + Patch by Bostik. + +Wed Feb 21 00:10:00 EET 2001 Pekka Riikonen + + * Associated the ID (client or server ID) to the Authentication + Payload to avoid any possibility of forging. Updated the + protocol specification and the code accordingly. + +Tue Feb 20 14:14:14 EET 2001 Pekka Riikonen + + * The RSA key length is now save to the RsaKey context in the + key generation process in lib/silccrypt/rsa.c. The key length + is now used to figure out the maximum size of the block allowed + to be encrypted/signed. + + * Added silc_mp_mp2bin_noalloc into lib/silcmath/mpbin.[ch]. It + is equivalent to the silc_mp_mp2bin but does not allocate any + memory. + + * Changed silc_mp_mp2bin API to take length argument. If it is + non-zero then the buffer is allocated that large. If zero, then + the size is approximated using silc_mp_sizeinbase, which however + is not relieable. + + * Created Global RNG API which is global RNG that application can + initialize. After initializing, any routine anywhere in the + code (including library) can use RNG without allocating a new + RNG object. This was done to allow this sort of use of the + RNG in code that has no chance to allocate RNG object. All + applications currently allocate this and many routines in the + library use this. Affected file lib/silccrypt/silcrng.[ch]. + + * Removed the RNG kludge from lib/silcmath/primegen.c and changed + it to use the Global RNG API. + + * Defined Authentication Payload into protocol specification that + is used during SILC session to authenticate entities. It is + used for example by client to authenticate itself to the server + to obtain server operator privileges. + + Implemented this payload into the lib/silccore/silcauth.[ch]. + Implemented also routines for public key based authentication + as the new protocol specification dictates. + + Moved definitions of different authentication methods from + lib/silccore/silcprotocol.h into lib/silccore/silcauth.h. + + * Added silc_pkcs_encrypt, silc_pkcs_decrypt, silc_pkcs_sign, + silc_pkcs_verify and silc_pkcs_sign_with_hash and + silc_pkcs_verify_with_hash functions into the file + lib/silccrypt/silcpkcs.[ch]. + +Mon Feb 19 19:59:28 EET 2001 Pekka Riikonen + + * The client entry's userinfo pointer must be always valid. + Otherwise the [] bug will surface beacuse the WHOIS + will fail since it requires the userinfo. Now, the userinfo + is allocated as "" if actual userinfo does not exist. Actually, + it must exist and it is totally Ok to drop client connections + that does not announce the userinfo. However, we will make + this workaround for now. + + * Added silc_net_get_remote_port into lib/silcutil/silcnet.[ch] + to return the remote port by socket. + +Mon Feb 19 14:26:49 EET 2001 Pekka Riikonen + + * Changed SILC_SERVER_COMMAND_EXEC_PENDING macro to the name + SILC_SERVER_PENDING_EXEC and added an new macro + SILC_SERVER_PENDING_DESTRUCTOR which is called to free the + data or when error occurs while processing the pending command. + + Added new argument `destructor' into silc_server_command_pending + and to the SilcServerCommandPending object. This destructor is + now called after calling the pending callback or if error occurs + immediately. If error occurs the actual pending callback won't + be called at all - only the destructor. The destructor may be + NULL if destructor is not needed. + + All this applies for client library code as well. Similar + changes were made there as well for the pending commands. + + In the client, the application must now allocate the + SilcClientCommandContext with the silc_client_command_alloc + function. + + * Added reference counter to the SilcServerCommandContext. Added + function silc_server_command_alloc and silc_server_command_dup + functions. + + Same type of functions added to the client library for the same + purpose as well. + + * Removed the cmd_ident from IDListData away since it is now + global for all connections. It is the command identifier used + in command sending and with pending commands. The affected file + is silcd/idlist.h. + + * Added reference counter to the SilcSocketConnection objecet to + indicate the usage count of the object. The object won't be + freed untill the reference counter hits zero. Currently only + server uses this, and client ignores it. The client must be + set to use this too later. The affected files are + lib/silccore/silcsockconn.[ch]. Added also the function + silc_socket_dup to increase the reference counter. + + This was mainly added because it is possible that the socket + is removed underneath of pending command or other async + operation. Now it won't be free'd and proper DISCONNECTING + flags, etc. can be set to avoid sending data to connection that + is not valid anymore. + + * Added SILC_SET_DISCONNECTING to server.c when EOF is read from + the connection. After that it sets SILC_SET_DISCONNECTED. + It is, however, possible that the socket data is not still freed. + The silc_server_packet_process now checks that data is not + read or written to connection that is DISCONNECTED. The socket + get's freed when the reference counter hits zero. + +Mon Feb 19 00:50:57 EET 2001 Pekka Riikonen + + * Changed the client operation API: channel_message operation's + `sender' is now the client entry of the sender, not the nickname + and the `channel' is the channel entry, not the channel name. + + In the private_message operation the `sender' is now also the + client entry of the sender not the nickname. + + Affected file is lib/silcclient/ops.h and all applications + using the client operations. + +Sat Feb 17 22:11:50 EET 2001 Pekka Riikonen + + * Moved the calling of ops->connect() from connect_to_server_final + into receive_new_id functin since that is the point when the + client is actually allowed to send traffic to network. The + affected file is lib/silcclient/client.c. + +Sat Feb 17 13:15:35 EET 2001 Pekka Riikonen + + * When receiving NEW_CHANNEL_LIST, NEW_CHANNEL_USER_LIST, + NEW_ID_LIST and SET_MODE_LIST packets, broadcast the list packet + (if needs broadcasting) instead of broadcasting the packets one + by one which would make a burst in the network traffic. + + * Added `broadcast' argument to the functions in silcd/server.[ch] + silc_server_create_new_channel[_with_id] to indicate whether + to send New Channel packet to primary router. + +Sat Feb 17 01:06:44 EET 2001 Pekka Riikonen + + * Added new function into the silcd/server.[ch] files: + silc_server_create_new_channel_with_id to create new channel with + already existing Channel ID. + + * Added new packet type SILC_PACKET_SET_MODE_LIST into the file + lib/silccore/silcpacket.h. This packet is used t send list of + Set Mode payloads inside one packet. Server uses this to set + the modes for the channels and clients on those channels, that it + announced to the router when it connected to it. The protocol + specification has been updated accordingly. + + * The silc_server_new_channel did not handle the packet coming + from normal server as it normally does not send that. However, + when it announces its channels it does send it. Implemented + the support for that. + + * Added SILC_ID_CHANNEL_COMPARE macro to compare to Channel ID's + into the file lib/silccore/id.h. + +Fri Feb 16 23:57:29 EET 2001 Pekka Riikonen + + * Fixed memory leaks in the functions silc_idlist_del_client, + silc_idlist_del_channel and silc_idlist_del_server in the file + silcd/idlist.c. All of those leaked like a sieve. + + * Fixed some small memory leaks in the client's function + silc_client_notify_by_server. + + * Added functions into silcd/server.c: silc_server_announce_clients, + silc_server_announce_channels and silc_server_announce_server. + These functions are used by normal and router server to announce + to its primary router about clients, channels and servers (when + router) that we own. This is done after we've connected to the + router. + + These functions effectively implements the following packet types: + SILC_PACKET_NEW_CHANNEL_LIST, SILC_PACKET_NEW_CHANNEL_USER_LIST + and SILC_PACKET_NEW_ID_LIST. + + * Added new functions into the silcd/packet_receive.[ch]: + silc_server_new_id_list, silc_server_new_channel_list and + silc_server_new_channel_user_list to handle the incoming + NEW_ID_LIST, NEW_CHANNEL_LIST and NEW_CHANNEL_USER_LIST packets. + + * Added support of changing Channel ID in the function + silc_server_replace_id. If the server that announces a channel + to the router already exists in the router (with same name but + with different Channel ID), router is responsible to send + Replace ID packet to the server and force the server to change + the Channel ID to the one router has. + + * Added new notify type SILC_NOTIFY_TYPE_CHANNEL_CHANGE to notify + client that the Channel ID has been changed by the router. The + normal server sends this to the client. Client must start using + the new Channel ID as the channel's ID. + + Implemented handling of this new type into lib/silcclient/client.c + into the function silc_client_notify_by_server. + + * Added new function silc_idlist_replace_channel_id into the files + silcd/idlist.[ch] to replace the Channel ID. + +Fri Feb 16 14:14:00 EET 2001 Pekka Riikonen + + * Call silc_server_command_identify_check always when processing + the IDENTIFY command in silcd/command.c + +Thu Feb 15 20:07:37 EET 2001 Pekka Riikonen + + * Added new packet type SILC_PACKET_HEARTBEAT that is used to + send keepalive packets. The packet can be sent by clients, + servers and routers. + + Added function silc_socket_set_heartbeat into the file + lib/silccore/silcsockconn.[ch] to set the heartbeat timeout. + If not set, the heartbeat is not performed. The actual + heartbeat is implemented in the low level socket connection + library. However, application is responsible of actually + sending the packet. + + Added silc_server_send_heartbeat to send the actual heartbeat + packet into silcd/packet_send.[ch]. Server now performs + keepalive with all connections. + + * Added silc_task_get_first function into lib/silcutil/silctask.c + to return the timeout task with shortest timeout. There was a bug + in task unregistration that caused problems. TODO has been + updated to include that task system must be rewritten. + + * The client library will now resolve the client information when + receiving JOIN notify from server for client that we know but + have incomplete information. + + * Rewrote parts of silc_server_remove_from_channels and + silc_server_remove_from_one_channel as they did not remove the + channel in some circumstances even though they should've. + + * Encryption problem encountered in server: + + The LEAVE command used to send the Channel Key packet to the + router immediately after generating it. However, the code + had earlier sent Remove Channel user packet but not immediately, + ie. it was put to queue. The order of packets in the router + was that Channel Key packet was first and Remove Channel User + packet was second, even though they were encrypted in the + reverse order. For this reason, MAC check failed. Now, this + is fixed by not sending the Channel Key packet immediately but + putting it to queue. However, this is more fundamental problem: + packets that are in queue should actually not be encrypted + because packets that are sent immediately gets encrypted + actually with wrong IV (and thus MAC check fails). So, packets + that are in queue should be encrypted when they are sent to + the wire and not when they put to the queue. + + However, the problem is that the current system has not been + designed to work that way. Instead, the packet is encrypted + as soon as possible and left to the queue. The queue is then + just purged into wire. There won't be any fixes for this + any time soon. So, the current semantic for packet sending + is as follows: + + o If you send packet to remote host and do not force the send + (the packet will be in queue) then all subsequent packets to the + same remote host must also be put to the queue. Only after the + queue has been purged is it safe again to force the packet + send immediately. + + o If you send all packets immediately then it safe to send + any of subsequent packets through the queue, however, after + the first packet is put to queue then any subsequent packets + must also be put to the queue. + + Follow these rules and everything works fine. + +Thu Feb 15 14:24:32 EET 2001 Pekka Riikonen + + * Added new function silc_server_remove_clients_by_server to + remove all client entries from ID list when the server connection + is lost. In this case it is also important to invalidate all + client entires as they hold the invalid server entry. This + fixes fatal bug when server has lost connection and will reconnect + again. + +Wed Feb 14 16:03:25 EET 2001 Pekka Riikonen + + * Made some sanity checks to silc_server_daemonise like to check + whether the requested user and group actually exists. + + * Added sanity check to SKE's silc_ske_responder_finish to check + that the public and private key actually is valid. + + * Invalidate the client's nickname when receiving Replace ID + packet and the Client ID is being replaced. This means that the + server will query the nickname if someone needs it (client) + a bit later. + + * Sort the ID Cache in client library when the ID Cache data + has changed (needs sorting). + + * Do not allow for SILC client to create several connections to + several servers. The client does not support windows right now + and generating multiple connections causes weird behaviour. + + Irssi-silc client does support windows and can handle several + connections without problems, see: www.irssi.org and SILC plugin. + + * Fixed some places where client was added to the IDList. The + rule of thumb is following (in order to get everything right): + If the client is directly connected local client then the + `connection' argument must be set and `router' argument must be + NULL to silc_idlist_add_client function. If the client is not + directly connected client then the `router' argument must + bet set and the `connection' argument must be NULL to the + silc_idlist_add_client function. + + * The funtion silc_server_packet_send_local_channel actually did + not check whether the client was locally connected or not. It + does that now. Fixed a bug related to LEAVE command. + + * Fixed Remove Channel User payload parsing bug in server's + silcd/packet_receive.c. Fixed a bug related to LEAVE command. + + * The server's silc_server_save_channel_key now checks also the + global ID list for the channel as it might not be in the local + list. Fixed a bug related to LEAVE command. + + * Is this the end of the [] buglet that has been lurking + around for a long time? A little for loop fix in server's + silc_server_command_whois_parse that is used by both IDENTIFY + and WHOIS command. At least, this was a clear bug and a cause + of one type of [] buglet. + + * WHOIS and IDENTIFY commands call the function + silc_server_command_[whois/identify]_check function even if + we are not router server. + +Tue Feb 13 19:55:59 EET 2001 Pekka Riikonen + + * Added --with-gmp configuration option. If set the GMP + is always compiled in the SILC source tree. If not set then + it is checked whether the system has the GMP3 installed. If + it has then the GMP won't be compiled (the system's headers + and library is used), if it doesn't have it then the GMP is + compiled in the SILC source tree. + +Mon Feb 12 11:20:32 EET 2001 Pekka Riikonen + + * Changed RSA private exponent generation to what PKCS #1 + suggests. We try to find the smallest possible d by doing + modinv(e, lcm(phi)) instead of modinv(e, phi). Note: this is + not security fix but optimization. + +Sun Feb 11 18:19:51 EET 2001 Pekka Riikonen + + * Added new config entry [Identity] to fork the server and run + it as specific user and group. A patch from Bostik. + + * Imported Dotconf configuration library into lib/dotconf. + This will be used to create the SILC configuration files later. + It will appear in the distsribution after this commit. + +Sat Feb 10 21:13:45 EET 2001 Pekka Riikonen + + * A big code auditing weekend happening. Auditing code for + obvious mistakes, bugs and errors. Also, removing any code + that is obsolete. + + Removed files for being obsolete: + + o lib/silcutil/silcbuffer.c (the buffer interface is entirely in + inline in the file lib/silcutil/silcbuffer.h) + + o lib/silcutil/silcbufutil.c (the header has inline versions) + + Changed code to fix possible error conditions: + + o The buffer formatting routines now check that the destination + buffer really has enough space to add the data. This applies for + both buffer formatting and unformatting + (lib/silcutil/silcbuffmt.[ch]). Also, the entire buffer + unformatting was changed to accomodate following rules: + XXX_*STRING_ALLOC will allocate space for the data into the pointer + sent to the function while XXX_*STRING will not allocate or copy + the data into the buffer. Instead it sets the pointer from the + buffer into the pointer sent as argument (XXX_*STRING used to + require that the pointer must be allocated already). This change + makes this whole thing a bit more consistent and more optimized + (note that the data returned in the unformatting with XXX_*STRING + must not be freed now). The routines return now -1 on error. + + o Tried to find all code that use buffer_format and buffer_unformat + and added return value checking to prevent formatting and + especially unformatting errors and possible subsequent fatal + errors. + + o Changed ske->x and ske->KEY to mallocated pointers in + lib/silcske/silcske.h. Fixed possible data and memory leak. + + o Added return value checking to all *_parse* functions. Fixed + many memory leaks as well. + + o Added length argument to silc_id_str2id in lib/silccore/id.[ch] + so that buffer overflows would not happen. All code now also + checks the return value as it can fail. + +Mon Feb 5 20:08:30 EET 2001 Pekka Riikonen + + * Added reconnection support to server if the normal server looses + its connection to the router (for example if router is rebooted). + The server performs normal reconnection strategy implemented + to the server. Affected file silcd/server.c. + +Sun Feb 4 13:18:32 EET 2001 Pekka Riikonen + + * Added new packet type SILC_PACKET_SET_MODE that is used to + distribute the information about changed modes (for clients, + channels and clients channel modes) to all routers in the + network. Updated the protocol specification accordingly. + + Added functions into silcd/packet_send.c and + silcd/packet_receive.c: silc_server_send_set_mode, + silc_server_set_mode. + + Added new files silcmode.[ch] into lib/silccore that implements + the encoding and decoding of Set Mode Payload. Added new type + SilcSetModePayload. Moved the definitions of different modes + from lib/silccore/silcchannel.h into lib/silccore/silcmode.h. + +Sat Feb 3 15:44:54 EET 2001 Pekka Riikonen + + * Oops, a little mistake in server's connection authentication + protocol. The protocol is not ended with FAILURE but with + SUCCESS if the authentication is Ok. :) Affected file is + silcd/protocol.c. + + * Implemented NICK_CHANGE notify handling in server in the file + silcd/packet_receive.c The NICK_CHANGE notify is distributed to + the local clients on the channel. After the changing nickname + in router environment snhould work and the [] nickname + should appear no more. + + The silc_server_replace_id function that receives the Replace ID + payload now sends the NICK_CHANGE notify type also in the file + silcd/packet_receive.c + + * Changed WHOIS and IDENTIFY command to support the maximum amount + of arguments defined in protocol specs (3328 arguments). This + fixed a bug that caused problems when there were more than three + users on a channel. + +Fri Feb 2 11:42:56 EET 2001 Pekka Riikonen + + * Added extra parameter, command identifier, to the + silc_client_send_command so that explicit command identifier + can be defined. + + Changed that ID list routines uses specific command identifier + when sending WHOIS/IDENTIFY requests to the server so that they + can be identified when the reply comes back. + + Affected files lib/silcclient/command.[ch], + lib/silcclient/client.c and lib/silcclient/idlist.[ch]. + + * Added `sender' argument to silc_server_packet_send_to_channel + to indicaet the sender who originally sent the packet to us + that we are now re-sending. Ignored if NULL. Affected file + silcd/packet_send.[ch]. + + * Added some server statistics support in silcd/server_internal.h + SilcServerStatistics structure and around the server code. Also + send some nice statistics information when client is connecting + to the client. + +Thu Feb 1 23:31:21 EET 2001 Pekka Riikonen + + * Fixed channel ID decoding in server's JOIN command reply in + silcd/command_reply.c + + * Fixed braodcasting of replace ID payload to not to send it if + we are standalone server in silcd/packet_receive.c. + + * Fixed all channel message sending routines to not to send + packets to clients that has router set, since they are routed + separately in the same function earlier. Affects file + silcd/packet_send.c and all channel packet sending functions. + + * In USERS reply, res_argv[i] are not allocated, the table + is allocated. Thus changed that free the table, not its + internals. + + * In server's whois_check and identify_check if the client is + locally connected do not send any WHOIS commands - they are not + needed. + +Thu Feb 1 21:32:27 EET 2001 Pekka Riikonen + + * Fixed some minor bugs in client when sending WHOIS command. The + arguments was in wrong order. + + * Removed statis function add_to_channel from server in + silcd/command.c that was previously used with the joining but + is obsolete now. + + * Tested USERS command in router environment successfully with two + routers, two servers and two clients. + +Thu Feb 1 00:54:26 EET 2001 Pekka Riikonen + + * Reorganized the USERS command and command reply in client library + in lib/silcclient/command.c and lib/silcclient/command_reply.c. + When the command is given by user we register a pending command + callback that will reprocess the command after the reply has been + received from the server. When reprocessing the packet we then + display the information. Thus, the USERS information is displayed + now in the command callback instead of in the command reply + callback. The processing of the command is same as previously + when server has sent the command reply in the JOINing process. + + * Added to USERS command in silcd/command_reply.c to join the client, + we didn't use to know about, to the channel after we've created + a client entry for it. Also, for clienet we did know already still + check whether it is on the channel or not and add it if not. + + * Removed silc_server_command_join_notify as the function and its + use was obsolete. + +Tue Jan 30 22:39:15 EET 2001 Pekka Riikonen + + * Changed the client's pending command handling to the same as the + server's pending command handling. It is also now possible to + execute command reply functions from other command reply + function as the function callbacks for commands and command + replies are one and same. The pending commands are not static + list anymore, it is mallocated SilcDList in lib/silcclient/client.h + in client connection context. Thus, pending commands are server + connection specific as it is convenient. + + Changed the function silc_client_command_pending and + silc_client_command_pending_del and added new function + silc_client_command_pending_check. Removed the + SILC_CLIENT_CMD_REPLY_EXEC, and SILC_CLIENT_PENDING_COMMAND_CHECK + macros. + + * Added cmd_ident, current command identifier, to the client + connection context in lib/silcclient/client.h to keep track on + command identifiers used in command sending. Client's command reply + function handling now supports the mandatory command identifiers. + + * Added SILC_CLIENT_COMMAND_EXEC_PENDING macros to all command reply + funtions in client to fully support pending command callbacks. + + * NOTE: the name_list in USERS (old NAMES) command is NOT sent anymore + as one of the arguments to the application in the command reply + client operation. + + * NOTE: The FORWARDED flag is depracated. It used to be depracated + before first releasing SILC but came back. Now it is removed again + and should come back nomore. The FORWARDED flag was used only + by the JOINing procedure by forwarding the command packet to router. + Now, the JOINing procedure has been changed to more generic (due + to various router environment issues) and FORWARDED is not needed + anymore for anything. The protocol specification is yet to be + updated. + + Now, removed silc_server_packet_forward from server and the flag + SILC_PACKET_FORWARDED from lib/silccore/silcpacket.h. + +Tue Jan 30 00:05:05 EET 2001 Pekka Riikonen + + * Renamed NAMES command to USERS command. The NAMES was named that + due to historical reasons. Now it is renamed. Also, rewrote + parts of the USERS command. The nickname list is not sent anymore + by the server. Only Client ID and mode lists are sent in the USERS + command. Changed this also to the protocol specification. + + The client now resolves the names and stuff after it receives + the USERS list from the server when joining to the channel. + + * WHOIS and IDENTIFY commands has been changed to support multiple + Client ID's per command. One can now search for multiple users + in the network by sending only one WHOIS or IDENTIFY command. + Changed the code and the protocol specifications. + + * Removed silc_server_command_identify_parse and changed that IDENTIFY + uses silc_server_command_whois_parse to parse the request. */ + + * If normal server, do not parse the WHOIS and IDENTIFY requests + before sending it to the router. Saves some time. + +Sun Jan 28 16:19:49 EET 2001 Pekka Riikonen + + * Fixed JOIN command on client library. Wrong number of arguments + used to crash the client. + + * Added silc_server_channel_has_global function to check whether + channel has global users or not. + + * Added silc_server_channel_has_local function to check whether channel + has locally connected clients on the channel. + + * The silc_server_remove_from_one_channel now checks whether the + channel has global users or not after given client was removed from + the channel. It also checks whether the channel has local clients + on the channel anymore. If it does not have then the channel entry + is removed as it is not needed anymore. + + * The silc_server_notify now checks on JOIN notify whether the joining + client is one of locally connected or global. If it is global then + the channel has now global users on the channel and that is marked + to the channel entry. Also, it now saves the global client to + global list who is joining and JOINs it to the channel. This is + for normal server, that is. + + Changed silc_server_send_notify_on_channel, + silc_server_packet_relay_to_channel and + silc_server_packet_send_to_channel check if we are normal server + and client has router set (ie. global client) do not send the + message to that client, as it is already routed to our router. + + * Implemented LEAVE notify type handling in silc_server_notify + function. + + * Tested LEAVE command in router environment successfully. Tested + with two routers, two servers and two clients. + + * Updated TODO. + + * idlist_find_xxx_by_id routines now dumps the ID on the debug mode. + + * Implemented SIGNOFF notify type handling in silc_server_notify + function. + + * silc_server_remove_id now removes the client entry from all channels + it has joined and thusly sends SIGNOFF notify type. + + * Rewrote the NAMES list generation in server by removing two excess + loops. The lists are created now inside one loop. + +Sat Jan 27 22:34:56 EET 2001 Pekka Riikonen + + * silc_server_remove_channel_user checks now also global list + for channel and client. + + * silc_server_new_channel_user checks now both local and global + list for channel and client. Fixed a bug in client id decoding. + Used to decode wrong buffer. + + * silc_server_channel_message checks now both local and global + list for channel entry. + + * Tested channel joining (hence JOIN) in router environment + successfully. Tested with two routers, two servers and two + clients. + + * Tested channel message sending in router environment successfully. + +Thu Jan 11 03:22:57 EET 2001 Pekka Riikonen + + * Added silc_server_save_channel_key into server.[ch] to save the + received channel key in Channel Key payload processing. It is + also used in JOIN command reply handling. + + Equivalent function silc_client_save_channel_key added into + client.[ch] into client library. + + * Changed JOIN command reply to send information whether the channel + was created or not (is existing already) and the channel key + payload. Changed protocol specs accordingly. + + * Fixed bugs in WHOIS and IDENTIFY command reply sending when + the request was sent by ID and not by nickname. Crashed on + NULL dereference. + +Sat Dec 23 21:55:07 EET 2000 Pekka Riikonen + + * Fixed a bug in Client library. IDENTIFY and WHOIS reply functions + now correctly save the received data. + + * silc_server_free_sock_user_data now notifies routers in the + network about entities leaving the network. + + At the same time implemented functions silc_server_remove_id + and silc_server_send_remove_id to receive and send REMOVE_ID + packets. The packet is used to notify routers in the network + about leaving entities. The ID removed will become invalid in + the network. + + * Added function silc_idlist_del_server into server. Removes and + free's server entry from ID list. + + * silc_server_private_message function now checks, if we are router, + that the destination ID really is valid ID, naturally. + + * In router when NEW_ID packet is received (for new client) the + hash of the Client ID is saved in the ID Cache but the + client->nickname is set to NULL, instead of putting the hash + to it as well. + + IDENTIFY command now also checks that client->nickname must be + valid. If it is not if will request the data from the server who + owns the client. Added new function + silc_server_command_identify_check. + + * Added silc_command_set_command into lib/silccore/silcommand.[ch] + to set the command to already allocated Command Payload. + + * Tested private message sending in router environment with two + routers, two servers and two clients. Fixed minor bugs and now + it works fine. + + * Fixed segfault from client's NAMES command. Used to crash if + not on any channel. + + * Forwarded packets must not be routed even if it is not destined + to the receiver. Changed server code comply with this. + +Sun Dec 17 14:40:08 EET 2000 Pekka Riikonen + + * Added `require_reverse_mapping' boolean value to ServerParams + structure. If TRUE (not default) the server will require that + the connecting host has fully qualified domain name. + + If the reverse mapping is not required and hostname could not be + found the IP address is used as hostname. + +Sat Dec 16 17:39:54 EET 2000 Pekka Riikonen + + * Implemented version string checking to both client and server. + The check is incomplete currently due to the abnormal version + strings used in development version of SILC. + + * Changed all command functions in server to use the new + CHECK_ARGS macro. + +Fri Dec 15 15:55:12 EET 2000 Pekka Riikonen + + * Changed char *data to unsigned char *data in ID Cache system to + support binary data as ID Cache data. Changed code to support + binary data in lib/silccore/idcache.c. + + * Renamed silc_server_packet_relay_command_reply to + silc_server_command_reply as it is normal packet receiving + function. Rewrote the function to accept command replys for + servers and not only for clients. + + * Mark remote router always as registered server if we are connecting + to it. Otherwise, commands sent by the router to us are ignored. + + * All ID List find routines now returns the ID Cache Entry pointer + as well if requested. + + * WHOIS command works now in router environment, tested with two + routers, two servers and two clients. + + * Cleaned up and rewrote IDENTIFY command. IDENTIFY should work now + in router environment (as it is almost equivalent to WHOIS) but + hasn't been tested thoroughly. Added new functions: + + silc_server_command_identify_parse + silc_server_command_identify_send_reply + silc_server_command_identify_from_client + silc_server_command_identify_from_server + + * Disabled route cache adding because adding two different ID's with + same IP replaces the old cache entry thus giving wrong route. + The entry->router->connection is always the fastest route anyway + so route cache may not be needed. Of course, new routes maybe + established after receiving the ID when the entry->router->connection + might not be anymore the most optimal. + +Thu Dec 14 15:55:35 EET 2000 Pekka Riikonen + + * Add route cache for received ID for fast routing. + + * Added silc_server_packet_route to route received packet on router + that is not destined to us. + + * Renamed silc_server_get_route to silc_server_route_get. + + * Added id_string and id_string_len fields into SilcServer to + include encoded ServerID for fast comparing without excess + encoding of the ID's. + + * Cleaned up WHOIS command on server side. Added following static + functions: + + silc_server_command_whois_parse + silc_server_command_whois_check + silc_server_command_whois_send_reply + silc_server_command_whois_from_client + silc_server_command_whois_from_server + + * Added macro SILC_SERVER_COMMAND_CHECK_ARGC to check mandatory + arguments in command replies. All command functions should be + updated to use this macro. + +Sun Dec 10 23:52:00 EET 2000 Pekka Riikonen + + * Minor typo fixes on command reply handling on server. + +Tue Nov 28 11:05:39 EET 2000 Pekka Riikonen + + * Added silc_server_command_add_to_channel internal routine to add + the client to the channel after router has created the channel and + sent command reply to the server. + + * Added generic silc_server_send_command to send any command from + server. + + * Use static buffer with ID rendering instead of duplicating data. + +Mon Nov 27 21:39:40 EET 2000 Pekka Riikonen + + * Fixed a channel user mode bug when joining to a channel server gave + everybody channel founder rights, oops. + + * We mark ourselves as the router of the incoming server connection + if we are router ourselves. This way we can check in some packet + sending functions whether it is locally connected server. For + incoming router connections we put NULL. + + * For router sending packets locally means now always sending the + packet cell wide; to local clients and local servers. For normal + server sending packet locally means sending it to only local + clients. + + * Fixed the JOIN command to really work in router environment. If the + channel is created it is always created by the router. Router is + also responsible of making the initial joining to the channel, + sending JOIN notify to the sending server and distributing + NEW_CHANNEL and NEW_CHANNEL_USER packets. Hence, if the channel + does not exist server doesn't do anything else but forward the + command to the router which performs everything. + + * Added silc_server_send_channel_key function to send the Channel Key + payload. + + * Added silc_server_create_channel_key to create new channel key. The + channel key is now re-generated everytime someone joins or leaves + a channel, as protocol dictates. Note: channel->key_len is the + key length in bits. + +Wed Nov 22 22:14:19 EET 2000 Pekka Riikonen + + * Splitted server.[ch] finally. Created now packet_send.[ch] and + packet_receive.[ch] to separate packet sending and receiving + routines. The server.[ch] now includes everything else including + actual packet processing (writing and reading data) and other + server issues. + + Renamed silc_server_private_message_send_internal to + silc_server_send_private_message. The routine is still though + used only to relay private messages as server does not send + private messages itself. + + Renamed silc_server_new_channel to silc_server_create_new_channel + and added new function sicl_server_new_channel that handles the + incoming New Channel packet. Added also new sending function + silc_server_send_new_channel to send New Channel Payload. + + * Added new function silc_server_notify to process incoming notify + packet to the server/router. Server may then relay the notify + to clients if needed. + + * Added new function silc_server_new_channel_user to process incoming + New Channel User packet. Router will redistribute the packet and + send JOIN notify to its local clients and locally connected servers + if needed. Normal server will send JOIN notify to its local client + on same channel when received this packet. Added also corresponding + sending function silc_server_send_new_channel_user to sent the + payload. + + * Added boolean route argument to send_notif_to_channel and + packet_send_to_channel functions to attempt to route the packet + if it is TRUE and send only locally if it is FALSE. + +Tue Nov 21 19:49:31 EET 2000 Pekka Riikonen + + * silc_server_replace_id now broadcasts the received replace ID + packet if it is not broadcast packet already. The router must + broadcast to inform other routers about changed ID. + + * Added backpointer to server's router into SilcServer context in + silcd/server_internal.h. + + * Fixed silc_server_packet_broadcast to send correct broadcast + packets. + + * The channel key is now distributed to the local client as soon + as it is received from the router (in router environment) so that + no other packet may be sent for the channel until client has + received the key. + + * silc_server_remove_channel_user now broadcasts the received + Remove Channel User packet if it is not broadcast packet already. + The router must broadcast to inform other routers about removed + channel user. + + * Added users field into SilcPacketContext that is a reference count + of the context. One can increase the reference count by calling + silc_packet_context_dup which is now changed to just increase the + reference count instead of duplicating the data. The reference + count is decresed by calling silc_packet_context_free that will + free the data after the reference count hits zero. + + For now on the packet context and everything allocated into it + (including the raw packet from network) must be freed by calling + the new silc_packet_context_free function. Added also new function + silc_packet_context_alloc that must be used now to allocate the + context. This also means that if a routine is asynchronous from + silc_[client/server]_packet_parse_type the packet context must + be duplicated by calling silc_packet_context_dup. Otherwise it + gets free'd after silc_[client/server]_packet_parse_type returns. + Also, one must remember that if packet is duplicated then its + reference count must be decresed by calling the free function as + many times as it was duplicated. + + * Changed SilcBuffer field from protocol contexts to SilcPacketContext + from both client and server. + +Mon Nov 20 23:47:03 EET 2000 Pekka Riikonen + + * Made joining to a channel working in router environment. + + * Cleaned up JOIN command on server side and create function + silc_server_command_join_channel internal routine to make the + joining happen. + +Thu Nov 9 21:12:39 EET 2000 Pekka Riikonen + + * Changed silc_command_pending list to SilcDList. Also, added + `ident' field to SilcServerCommandPending structure to identify + the reply and to call correct callback. + + Added silc_server_command_pending_check function to replace the + corresnponding macro. The silc_command_pending list is not + extern anymore. + + * Added silc_command_set_ident into lib/silccore/silccommand.[ch] + to set identifier to previously allocated Command Payload. It + is used to set identifier for command when resending Command + Payload. + + * Added silc_command_payload_encode_payload to encode Command + Payload buffer from SilcCommandPayload structure. + + * Added silc_argument_payload_encode_payload to encode Argument + payload buffer from SilcArgumentPayload structure. + +Wed Nov 8 21:03:28 EET 2000 Pekka Riikonen + + * Changed WHOIS command to support router connection on server side. + The whois request is always sent to router unless the server is + standalone server. After server has received the reply from the + router will it send the reply to the client. + + * Added silc_server_packet_broadcast into silcd/server.[ch] to + broadcast received broadcast packet. The function is used only + by router. The broadcast packet is always sent to the router's + primary route. + + * Added silc_id_render function in lib/silcutil/silcutil.[ch] to + render given ID to printable string, for log files for example. + +Tue Nov 7 22:14:19 EET 2000 Pekka Riikonen + + * Made basic router to router connections working. At least they + can now connect to each other but nothing really works the way + they are supposed - yet. + + * Added new initiator token to RouterConnection configuration + file in silcd/serverconfig.[ch]. It is used to tell whether we + are the initiator to the remote router or whether we'll expect + the other end to connect. + + * Moved registering of listener task to silc_server_init, hence + the server starts listenning as soon as it is run, even if it + does not have connections to other routers. Let's see how well + this will work. + + * Changed default connection retry timeouts for more suitable in + silcd/server.h. + + * Removed cipher and such arguments from silc_idlist_add_client + and silc_idlist_add_server prototypes from silcd/idlist.[ch]. + Added new function silc_idlist_add_data to add the keys and stuff + to any ID entry. + + * Added SilcIDListData structure and added it to SilcClientEntry + and SilcServerEntry as their first field in the structure. This + way we can explicitly cast the ID entries to the SilcIDListData + structure and get common data for the entries. In past, we had + to first check what type of connection it is and then cast it to + correct ID entry type. Now, we can directly cast the opaque + pointer to the SilcIDListData (no matter what ID entry it actually + is) and get the data needed. + +Mon Nov 6 21:56:12 EET 2000 Pekka Riikonen + + * Wow, found a bug in scheduler. The scheduler uninitialized itself + in some circumstances even if threre were timeout tasks, though not + IO tasks, but tasks anyway. Now fixed. + + * Defined SilcServerConnection structure to hold connection specific + stuff about directly connected servers and routers. The definition + is currently in silcd/server_internal.h. I thought about having + a bit more important role fro this struct but for now it is used + only when connecting to other server (or router actually). + + * Added connecting retry support in server when connecting to + router(s). The retry feature implement exponential backoff + algorithm. Also, added SilcServerParams structure to hold default + parameters for server. For now, it include these retry settings + and are hard coded. After server is moded to be as Silc Server + Library this structure will be more important. + +Sun Nov 5 22:28:44 EET 2000 Pekka Riikonen + + * Changed client librarys channel->clients table to SilcList and + changed code accordingly. + +Thu Nov 2 16:28:01 EET 2000 Pekka Riikonen + + * Changed client's channel table to SilcList and changed code + accordingly. Also changed SilcChannelClientEntry to include back- + pointer to the channel so that client entry can use that structure + as list as well and we have fast cross-reference to the channel. + This change dramatically decreased the complexity of channel + handling with client entry and vice versa (removed one extra + loop when searching for channel entry from many functions). + + * Changed server->sim from table to SilcDList and changed code + accordingly. + + * NAMES command can now be used from user interface. It will show + the user list on the channel, neatly. + + * Added realname pointer to SilcClientEntry in lib/silcclient/idlist.h. + Code now saves realname of the user if it becomes available. + + * Renamed configure.in to configure.in.pre and made ./prepare + script to automatically add correct version string to + configure.in which it creates from configure.in.pre. + +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 --git a/CREDITS b/CREDITS new file mode 100644 index 00000000..c43ad28e --- /dev/null +++ b/CREDITS @@ -0,0 +1,66 @@ +---------------------------------------------------------------------------- + +This is the credits file of people that have contributed to the SILC +project. It is supposed to by sorted by name and of the following format: +name (N), email (E), web-address (W), PGP key ID and fingerprint (P), +description (D), and snail-mail address (S). The format is from the Linux +kernel credits file. :) + +---------------------------------------------------------------------------- + +N: Mika "Bostik" Boström +E: bostik@lut.fi +W: http://www.lut.fi/~bostik +D: Server runs as a daemon on dedicated account +D: Fixed inconsistencies in server side code routine names +D: Transition to new configuration file format +S: Skinnarilankatu 28 E 2 +S: 53850 Lappeenranta +S: Finland + +N: Pekka Riikonen +E: priikone@silcnet.org +E: priikone@ssh.com +P: 1024/A924ED4F AD 82 53 2D 84 FF C7 D1 FF 63 19 0E 1A 78 9F 8A A9 24 ED 4F +D: Protocol architet +D: Main developer +S: Snellmanninkatu 34 A 15 +S: 70100 Kuopio +S: Finland + +N: Juha Räsänen +E: jmrasane@lut.fi +D: Persistent nagger +D: Desultory coder +D: ElGamal implementation +S: Laserkatu 4 A 4 +S: 53850 Lappeenranta +S: Finland + +N: Lubomir Sedlacik +E: salo@Xtrmntr.org +W: http://Xtrmntr.org +P: 1024/7E3B70E2 DB EC 8B EC 9A 90 EC EC 0F EF 71 6E 59 CE B7 0B 7E 3B 70 E2 +D: webpage +D: configure.in bugfixes +S: A.Hlinku 59/83 +S: 92101 Piestany +S: Slovakia + +N: Timo Sirainen +E: tss@iki.fi +W: http://www.irssi.org +D: Irssi/SILC client +D: Several bugfixes +S: Lintulahdenaukio 8 as 30 +S: 00500 Helsinki +S: Finland + +N: Saku Ytti +E: saku@ytti.fi +D: wannabe +S: Pursimiehenkatu 16 d 60 +S: 00150 Helsinki +S: Finland + +---------------------------------------------------------------------------- diff --git a/INSTALL b/INSTALL index c2d5f4b5..de90a118 100644 --- a/INSTALL +++ b/INSTALL @@ -1,23 +1,217 @@ -Installing SILC Developer's Version -=================================== +Quick Installation +================== -./configure -./make +To configure and compile SILC package give the comands: -You should not install the SILC into your system, instead, you should -run it from the current directory. + ./configure + make (or gmake) + make install -To see different compilation options, give, +This will install the SILC binaries and configuration files into the +/usr/local/silc/ directory. System wide configuration files are installed +into the /etc/silc/ directory. -./configure --help +Some Configuration Options +========================== + You can give various options to the `configure' shell script. You should +give --help command to the `configure' to see all of them. Here is listed +few options that you might want to use. Please refer to the rest of this +file for more generic installation instructions. -Generally, developers wants to compile with debugging, in this case, -give, +--with-gmp=PATH -./configure --enable-debug + If you wish to use GMP library for arbitrary precision arithmetic +library instead of using the MPI library included in the package, you can +give the --with-gmp=PATH option to the `configure'. The PATH is the path +to the GMP library in your system. -WARNING: The debugging is very very heavy and you currently cannot turn -it off if you have compiled it with this option. However, if you're -going to develop or debug SILC you whould compile with this option. +--disable-asm + If you have trouble compiling the assembler optimized code in the +package or does not want to use them, you can give the --disable-asm +option to the `configure' script. This will assure that assembler +optimized code is not compiled in. + +--enable-debug + + If you would like to enable the debugging for the compiled programs +you can give this option to the `configure'. + +--disable-threads + + If you do not want to compile the programs with multi threads support +you can give --disable-threads option. In this case all compiled programs +will work in single thread only. + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. diff --git a/Makefile.am.pre b/Makefile.am.pre new file mode 100644 index 00000000..5c0696c5 --- /dev/null +++ b/Makefile.am.pre @@ -0,0 +1,88 @@ +# +# Makefile.am +# +# Author: Pekka Riikonen +# +# Copyright (C) 2000 - 2001 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. +# + +AUTOMAKE_OPTIONS = 1.0 no-dependencies foreign + +COMMONDIRS = lib irssi silc silcd doc includes +SUBDIRS = SILC_DISTRIBUTION_SUBDIRS +DIST_SUBDIRS = SILC_DISTRIBUTION_SUBDIRS + +include $(top_srcdir)/Makefile.defines.in + +dist-bzip: distdir + -chmod -R a+r $(distdir) + -tar chof $(distdir).tar $(distdir) + -bzip2 $(distdir).tar + -rm -rf $(distdir) + +SILC_EXTRA_DIST = SILC_DISTRIBUTION_EXTRA +EXTRA_DIST = CHANGES CREDITS $(SILC_EXTRA_DIST) + +# +# Installing of SILC into the system +# + +etcdir = $(DESTDIR)$(silc_etcdir) +modulesdir = $(DESTDIR)$(silc_modulesdir) +helpdir = $(DESTDIR)$(silc_helpdir) +docdir = $(DESTDIR)$(silc_docdir) +logsdir = $(DESTDIR)$(silc_logsdir) + +install-dirs: + -mkdir -p $(etcdir) + -mkdir -p $(modulesdir) + -mkdir -p $(helpdir) + -mkdir -p $(docdir) + -mkdir -p $(logsdir) + +generate-server-key: + -@if test '!' -f $(etcdir)/silcd.pub ; then \ + $(sbindir)/silcd -C $(etcdir); \ + fi + +sim-install: + -cp -fR $(srcdir)/lib/silcsim/modules/*.so $(modulesdir)/ + +doc-install: + $(INSTALL_DATA) $(srcdir)/doc/CodingStyle $(docdir)/ + $(INSTALL_DATA) $(srcdir)/doc/FAQ $(docdir)/ + $(INSTALL_DATA) $(srcdir)/doc/example_* $(docdir)/ + $(INSTALL_DATA) $(srcdir)/doc/*.txt $(docdir)/ + $(INSTALL_DATA) $(srcdir)/COPYING $(docdir)/ + $(INSTALL_DATA) $(srcdir)/CHANGES $(docdir)/ + $(INSTALL_DATA) $(srcdir)/CREDITS $(docdir)/ + $(INSTALL_DATA) $(srcdir)/README $(docdir)/ + $(INSTALL_DATA) $(srcdir)/INSTALL $(docdir)/ + $(INSTALL_DATA) $(srcdir)/TODO $(docdir)/ + +etc-install: + -@if test '!' -f $(etcdir)/silcd.conf ; then \ + $(INSTALL_DATA) $(srcdir)/doc/example_silcd.conf \ + $(etcdir)/silcd.conf; \ + chmod go= $(etcdir)/silcd.conf; \ + fi + -@if test '!' -f $(etcdir)/silc.conf ; then \ + $(INSTALL_DATA) $(srcdir)/doc/example_silc.conf \ + $(etcdir)/silc.conf; \ + fi + +if SILC_DIST_CLIENT +install-data-hook: install-dirs sim-install doc-install etc-install +else +install-data-hook: install-dirs generate-server-key sim-install doc-install etc-install +endif diff --git a/Makefile.defines.pre b/Makefile.defines.pre new file mode 100644 index 00000000..af48710c --- /dev/null +++ b/Makefile.defines.pre @@ -0,0 +1,68 @@ +# +# Makefile.defines.pre +# +# Author: Pekka Riikonen +# +# Copyright (C) 2001 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. +# + +# +# This file is intended to include all common compilation defines for the +# SILC source tree. All Makefile.ams in the SILC source tree are expected +# to include this file (Makefile.defines.in). Also this file may be included +# in any external project that is included in the SILC source tree. +# +# Add following to your Makefile.am: +# +# include $(top_srcdir)/Makefile.defines.in +# +# All packages in the SILC source tree that include the Makefile.defines.in +# must also include the following two lines in their configure.in file. +# +# INCLUDE_DEFINES_INT="include \$(top_srcdir)/Makefile.defines_int" +# AC_SUBST(INCLUDE_DEFINES_INT) +# +# (See the Makefile.defines_int.pre for all different definitions but DO NOT +# directly include that file!) +# + +@INCLUDE_DEFINES_INT@ + +# +# INCLUDE defines +# +INCLUDES = $(ADD_INCLUDES) $(SILC_CFLAGS) \ + -I$(srcdir) -I$(top_srcdir) \ + -I$(silc_top_srcdir) \ + -I$(silc_top_srcdir)/lib/silccore \ + -I$(silc_top_srcdir)/lib/silccrypt \ + -I$(silc_top_srcdir)/lib/silcmath \ + -I$(silc_top_srcdir)/lib/silcmath/mpi \ + -I$(silc_top_srcdir)/lib/silcske \ + -I$(silc_top_srcdir)/lib/silcsim \ + -I$(silc_top_srcdir)/lib/silcutil \ + -I$(silc_top_srcdir)/lib/silcsftp \ + -I$(silc_top_srcdir)/lib/silcclient \ + -I$(silc_top_srcdir)/lib/contrib \ + -I$(silc_top_srcdir)/includes \ + -I$(silc_top_srcdir)/doc \ + -I$(silc_top_srcdir)/lib/trq + +# +#includes-install: Makefile +# for i in $(include_HEADERS); do s=$(srcdir)/$$i; +#d=$(silc_top_srcdir)/includes/$$i; \ +# ln $$s $$d 2>/dev/null || (rm -f $$d; cp -p $$s $$d;); \ +# done; +# +#all-local: includes-install diff --git a/Makefile.defines_int.pre b/Makefile.defines_int.pre new file mode 100644 index 00000000..4c7f4145 --- /dev/null +++ b/Makefile.defines_int.pre @@ -0,0 +1,47 @@ +# +# Makefile.defines_int.pre +# +# Author: Pekka Riikonen +# +# Copyright (C) 2000 - 2001 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. +# + +# +# Internal file for definitions. This is read by Makefile.defines. DO NOT +# include this file directly to your Makefile.ams. +# + +# +# Generic definitions +# +silc_top_srcdir=@SILC_TOP_SRCDIR@ +silc_install_prefix=@prefix@ + +# +# Common libraries that are linked against the created executable +# +SILC_COMMON_LIBS= @LIBS@ -L$(silc_top_srcdir)/lib -lsilc + +# +# Common compilation flags +# +SILC_CFLAGS=@CFLAGS@ + +# +# Installation defines +# +silc_etcdir=@ETCDIR@ +silc_modulesdir=@MODULESDIR@ +silc_helpdir=@HELPDIR@ +silc_docdir=@DOCDIR@ +silc_logsdir=@LOGSDIR@ diff --git a/README b/README index 1953d942..78f394db 100644 --- a/README +++ b/README @@ -1,27 +1,32 @@ SILC - Secure Internet Live Conferencing ======================================== -[NOTE: SILC is still in middle of development and this package is known -as Developer's Version which means that the package is in no means stable -or ready to be in production use. This package is for those who wants -to test SILC, find bugs and maybe contribute some time and code for the -SILC project. There is no guarantees that this package even compiles and -even if it compiles there is no guarantees that it would work, and even -if it works there is no guarantees that it would work correctly, and even -if it seems to work correctly it may be just plain luck.] +SILC (Secure Internet Live Conferencing) is a protocol which provides +secure conferencing services on the Internet over insecure channel. +SILC is IRC-like software although internally they are very different. +The biggest similarity between SILC and IRC is that they both provide +conferencing services and that SILC has almost the same commands as IRC. +Other than that they are nothing alike. Major differences are that SILC +is secure what IRC is not in any way. The network model is also entirely +different compared to IRC. -Description -=========== +Running SILC +============ -SILC (Secure Internet Live Conferencing) is a protocol which provides -secure conferencing services in the Internet over insecure channel. -SILC is IRC like softwarre although internally they are very different. -Biggest similiarity between SILC and IRC is that they both provide -conferencing services and that SILC has almost same commands as IRC. Other -than that they are nothing alike. Biggest differences are that SILC is -secure what IRC is not in any way. The network model is also entirely -different compared to IRC. +After installing the SILC to the system the SILC client is started by +giving command: + + silc + +If you want to run with specific configuration file give -f option. + +To run the server you should configure the server first. To run the +server give the command: + + silcd + +This will launch the server on to the background. Features @@ -55,6 +60,8 @@ TODO file for more information.] o Supports data compression with GZIP to improve performance. + o Supports SOCKS4 and SOCKS5 firewall traversal protocols. + o SIM (SILC Module) support. Support for loading of shared objects at run-time that provides new and extended features to both SILC client and server. These can provide extra ciphers and extra features to @@ -69,63 +76,43 @@ TODO file for more information.] History ======= -Even though SILC were just released to the public the idea and the protocol -itself is quite old. I got the idea about SILC in its current form in -the year 1996 and first lines of codes were written in early 1997. This -release is now third rewrite of the SILC. The very first version were -written in 1997 and it included SILC client and very very preliminary -SILC server. The server actually weren't usable but the client looked -pretty much the same as it does now. At that time the SILC also included -RSA implementation and 3DES implementation. The random number generator -that exists in this current release is actually based on the RNG written -in 1997. The RNG written in 1997, on the other hand, were based on -the SSH's random number generator. The RNG has been rewritten twice -since the first version. - -I stopped writing the SILC later in 1997 when I got busy at school and -in work. The pause lasted several months. The development resumed in -1998 when my friend (Juha Räsänen) and I implemented ElGamal algorithm. -I rewrote some other parts as well. However, for the same reasons as -previously the development stopped again. I resumed the development -later in 1998 by doing rewrite of the SILC in C++. This was obviously -a mistake but at that time it seemed like a good idea. Again, in the -winter 1999 I got very busy writing my thesis and was forced to stop the -development again. I also, started a new job in the spring. - -Later, in 1999, I decided that this time I'm going to make it the right -way. C++ was obviously a bad choice so I decided to fall back to plain -C language. I also decided to do complete rewrite and started doing -more thorough planning of what the SILC actually should include. I also -decided that this time it is going to kill me before I stop the -development. I started writing SILC in the weekends and actually -everytime I had some spare time. I also started a new job but I didn't -let that get to my way. The result of this development effort is the -release now in public. - -I've learned a lot by doing the SILC. I guess, when I started it I wasn't -that good of a C programmer. That alone was a reason why SILC hasn't -seen the day of light before now. My programming style has also changed -dramatically during these years. Actually, it has changed couple times -since this last rewrite as well. However, the code style of current SILC -release is quite consistent (actually the coding style SILC has been -written now I've learned in my current job). - -There is probably over 85% of new code in this third rewrite. Rest has -just been copied from the old versions and only minor changes has been -made (like changed function names and overall coding style). I've -preserved the dates of the old files (dating back to 1997) that has -existed in some forms in the old versions. There is a lot of new code but -already I see a lot that needs rewriting. The development continues. +SILC was released in the summer 2000 to the public, but the idea and the +protocol itself is quite old. The SILC was designed by Pekka Riikonen in +the year 1996 and first lines of codes were written in the early 1997. The +SILC has been rewritten three times since its very first version in 1997. +The first version included SILC client, very preliminary SILC server, RSA +implementation and 3DES implementation. The server actually was not usable +but the client looked pretty much the same as the first client released in +the summer 2000. The first version had also random number generator which +were based on the SSH's random number generator. The current RNG is based +on the first RNG but has been rewritten twice since the first version. + +The development of SILC was suspended in 1997 when Pekka got busy at +school and in work. The pause laster several months. The development +resumed in 1998 when Juha Räsänen and Pekka implemented the ElGamal +algorithm. However, for the same reasons as previously the development +stopped again, and was resumed again later in 1998 by doing rewrite of +ther SILC in C++. This was obviously a mistake but at that time it seemed +like a good idea. Again, in the winter 1999 the development suspended when +Pekka got busy writing his thesis and was forced to stop the development. + +Later, in 1999, it was decided that this time SILC will be rewritten from +scratch in the right way. C++ was obviously a bad choice so plain C +language was selected again. The protocol itself faced some rework by +redesigning some core parts of the protocol. The protocol was also fully +documented and the protocol specifications were submitted to the IETF. The +result of this development effort is the release now in public. Since the +release in the summer 2000 several other people have contributed to the +project as well. And, the development continues. Contact ======= -Feedback and comments are welcome. You can reach me in the following -Address. - -[Note that generally bug reports should not be sent just yet as the -Developer's Version is full of them and the bug hunt has not even started -yet.] +Feedback and comments are welcome. Bug reports should be sent to the +development mailing list. - Pekka Riikonen +Official SILC project web site : http://silcnet.org/ +FTP archive for SILC project : ftp://ftp.silcnet.org/ +Development mailing list address : silc-devel@lists.sourceforge.net +SILC Server : /server silc.silcnet.org diff --git a/README.CVS b/README.CVS new file mode 100644 index 00000000..3c0f45dc --- /dev/null +++ b/README.CVS @@ -0,0 +1,207 @@ +Anonymous CVS Access +==================== + +Anonymous CVS access is now available to SILC CVS repository. The +repository includes everything related to SILC project; source codes, +documentation and web pages. + +Also note that this is the closest to real time development you can get +thus you cannot expect that the source tree would work or even compile. +While it is our intention that the trunk would always at least compile +there might be situations when it will not. + + +Howto Checkout The Source Tree +============================== + +The repository can be checked out by using anonymous pserver with CVS. +There are no password restrictions in the SILC anonymous CVS repository. + +For those who are using sh/ksh/bash the check out is done as follows: + +export CVSROOT=:pserver:cvs@cvs.silcnet.org:/cvs/silc +cvs login +cvs co silc + +For those who are using csh/tcsh the check out is done as follows: + +setenv CVSROOT :pserver:cvs@cvs.silcnet.org:/cvs/silc +cvs login +cvs co silc + +If you don't want to set $CVSROOT environment variable you can set the +path to the cvs as command line options: + +cvs -d:pserver:cvs@cvs.silcnet.org:/cvs/silc login +cvs -d:pserver:cvs@cvs.silcnet.org:/cvs/silc co silc + +What ever method you decide to use, after you have done cvs login you will +be prompted for password: + + CVS password: silc + +Type the password "silc" and press Enter. + +The actual SILC source tree is checked out using the cvs co silc command, +described above. This command will fetch the source tree and save it into +directory named silc. SILC CVS repository currently does not have any +branches thus this will check out the trunk. The size of the trunk is +currently about 8 Mb but will grow in the future. + + +What SILC Source Tree Includes +============================== + +SILC Source tree includes a lot more stuff that appears in public +distribution. The source tree includes, for example, internal scripts, +configuration files, SILC webpages etc. These never appear on a public +distribution. + +Following directories currently exist in SILC source tree. + + doc/ + + Includes all the SILC documentation. Some of the documentation + are generated when distribution is generated. The automatically + generated files must never be commited to CVS. + + includes/ + + Includes SILC include files. + + irssi/ + + Includes the Irssi SILC Client. + + lib/ + + Includes SILC libraries. There maybe libraries on the CVS that + does not appear on public distribution. + + lib/contrib/ + + Contrib directory for routines that some of the platforms might + not have. In that case these routines are provided by the SILC. + + lib/silcclient/ + + The SILC Client library. Implementation of the SILC Client without + the user interface. The library provides an interface for user + interface designers. + + lib/silccore/ + + The SILC Protocol Core library. Implementation of all the core + components of the SILC Protocol. This is used by all the SILC + applications. + + lib/silccrypt/ + + The SILC Crypto library. Provides all cryptographic algorithms + used in the SILC. Provides also the Cryptographically strong + random number generator. + + lib/silcmath/ + + The SILC Math library. Provides the Math and MP routines for + SILC applications. The MP library is actually the GMP. + + lib/silsim/ + + The SILC Modules library. Provides the dynamically loadable + modules. + + lib/silcske/ + + The SILC Key Exchange (SKE) library. Implementation of the + SKE protocol. This is used by all SILC applications. + + lib/silcutil/ + + The SILC Utility library. Provides various utility functions + for the applications. + + lib/silcutil/unix/ + + The SILC Utility library. Provides various Unix specific utility + functions for the applications. + + lib/silcutil/win32/ + + The SILC Utility library. Provides various WIN32 specific utility + functions for the applications. + + public_html/ + + Includes the official SILC web pages and everything that relates + to them. This directory never appears on public distribution. + + silc/ + + Includes SILC client. There can be some extra files that will + never appear in public distribution, such as, configuration files. + + silcd/ + + Includes SILC server. There can be some extra files that will + never appear in public distribution, such as, configuration files. + + +Howto Compile SILC Source Tree +============================== + +After checkout from CVS the SILC source tree must be prepared for +configuration and compilation. To compile the source tree, give, + + ./prepare + ./configure --enable-debug + make + +The ./prepare script is included in to the source tree and it never +appears in public distribution. The script prepares the source tree +by creating configuration scripts and Makefiles. The prepare must be +run every time you make some changes to configuration scripts (however, +making changes to Makefile.am's does not require running ./prepare). + +As a developer you should read the ./configure script's help by +giving ./configure --help and study all of its different options. Also, +you should configure the script with --enable-debug option as it +compiles SILC with -g (debugging) option and it enables the +SILC_LOG_DEBUG* scripts. Warning is due here: The debugging produced +by both cilent and server is very heavy, thus it is common to test +the programs as follows: + + ./silc -d -f configfile 2>log + ./silcd -d -f configfile 2>log + +Do not give the -d options if you do not want to dump the debugging. + + +Howto Clean SILC Source Tree +============================ + +To entirely clear the source tree to the state after it was checked out +from CVS, give, + + ./prepare-clean + +This calls `make distclean' plus removes automatically generated files +by hand. It also removes *.log files. However, it will not remove +any other files you might have created. + + +Makefiles and configuration files +================================= + +Developers should never directly write a Makefile. All Makefiles are +always automatically generated by ./prepare and later by ./configure +scripts. Instead, developers must write Makefile.am files. There +are plenty of examples what they should look like. If you change +Makefile.am during development you don't have to run ./prepare, just +run normal make. + +Configuration files are the files that ./prepare automatically generates +and what will be included into public distribution. ./prepare creates +for example the ./configure script that is not commited to the CVS. +`configure.in' is the file that developers must edit to change ./configure +script. After changing one must run ./prepare. diff --git a/README.WIN32 b/README.WIN32 new file mode 100644 index 00000000..14364bc2 --- /dev/null +++ b/README.WIN32 @@ -0,0 +1,71 @@ +Compiling SILC Toolkit on WIN32 +=============================== + +SILC Toolkit works on native WIN32 systems as well. This document is +intended for those who needs to compile the Toolkit for native WIN32 +systems. The Toolkit can be compiled for native WIN32 systems using +generally any compiler. However, the compilation environment is designed +to currently work with the MSVC++ (version 6.0) and with the MinGW (under +cygwin). + + +Compiling SILC Toolkit with MSVC++ +================================== + +The MSVC++ workspace and project files resides in the win32/ subdirectory +of the Toolkit package. The `silc.dsw' file is the workspace file that +automatically supports compiling the Toolkit and to generate the SILC Core +DLL and SILC Client DLL libraries. + +The SILC Core DLL is named as libsilc and will generate libsilc.dll, and +the SILC Client DLL is named as libsilcclient and will generate +libsilcclient.dll. Both of the projects also automatically generates +libsilc.lib and libsilcclient.lib import libraries that may be used to +link against a client application. + +Generally you do not need to do any specific settings to compile the +Toolkit. However, you must compile the libsilc before compiling the +libsilclient, since the SILC Client DLL depends on the SILC Core DLL. + +You may compile the DLLs as either Release or Debug version. Just select +the preferred method of compilation. The Debug version will compile the +SILC Toolkit with debugging which you can conditionally use in your client +application by setting the global variable silc_debug to TRUE or FALSE. + + +Compiling SILC Toolkit with MinGW +================================= + +To compile the Toolkit with MinGW you first need to install the cygwin and +the MinGW into your system. After that you can just normally give the +./configure with the following option: + + ./configure --with-win32 + +If you want to compile debug version give also the --enable-debug option +to the ./configure. After configuration the source tree is ready for +compilation which you can simply start by giving the command: + + make + +Note that some of the subdirectories in the Toolkit will not compile under +WIN32 (namely the silcd/ that includes the SILC Server). For this reason +it is suggested that you will give the command make in the lib/ directory +to compile the DLLs. Thus, you should give the following commands after +giving the ./configure. + + cd lib + make + +After compilation there should be silc.dll and silcclient.dll files in +the lib/ directory. It will also generate silc.lib and silcclient.lib +files for linking against a client application. + + +Compiling SILC Toolkit with Cygwin +================================== + +Compiling the Toolkit with Cygwin is equivalent to compiling with MinGW +except that the ./configure does not take the --with-win32 option. In this +case it will compile using Cygwin's libraries and the binaries will require +the Cygwin DLL. diff --git a/TODO b/TODO index ece615c1..f8506a6d 100644 --- a/TODO +++ b/TODO @@ -1,261 +1,130 @@ -TODO -==== +TODO/bugs in Irssi SILC client +============================== -This is more or less complete list of tasks that has to be done before -SILC 1.0 could ever be released. It is clear that the list does not -include all the bugs that exists. At the end of list are tasks that -needs to be done but are probably post 1.0. + o /NAMES kees showing things wrong after JOIN and after ppl has left + channel. -Feel free to contribute if you have the ability and free time - all the -help is really appreciated - and needed. + o Add local command to switch the channel's private key when channel has + several private keys. Currently sending channel messages with many + keys is not possible because changing the key is not possible by the + user. - - Pekka + o JOINing to +a (requires passphrase to JOIN) does not work on autojoin. + Seems the passwords in the .silc/config has no effect. -[Latest Note: The protocol has changed a bit in some parts which -causes that the current implementation violates some requirements. -These are not listed here, currently.] + o Add local commands to list the current server and client public keys + that the user has. And a local command to dump the contents of the + public key to the screen. Something like LISTKEYS, SHOWKEY... + o The JOIN command's HELP is generated from Irssi IRCs JOIN help and + the syntax is not same in SILC. This must be fixed. Most likely + we must forget the Irssi's JOIN command and mimic it to get our + required syntax for it too. -New features TODO -================= + o We should get rid of the clientconfig.[ch] in Irssi SILC and move the + cipher, hash, hmac and pkcs configuration to the Irssi SILC's config + file. - o Extended SIM (SILC Module) support. Currently only SILC Cipher API - and SILC Hash API may be used as SIM's. What I have in mind is to - have extended support for SIM's so that basically any SILC API could - be used as SIM's. This would open tremendous possiblities but - opens also issues on security that needs to be dealt with. + o Add PERL scripting support from Irssi CVS. - Some sort of SIM compilation environment should be defined so that - the SIM's could use SILC specific symbols from the modules (which they - cannot do currently). In the future modules could add new features - to SILC easily with this support. I'm more thinking this from client's - perspective to add new features to client (such as IRC support as SIM) - but server should have the support as well. Anyhow, this is an - interesting feature... + o Extend the /HELP command to support sub commands or something. So + that user can say /help set mutual_authentication they would get + help of the mutual_authentication setting. - This maybe post 1.0 task - dunno. + o Set different kind of settings, like, /set mutual_authentication, + /set key_exchange_timeout, /set conn_auth_timeout etc etc. - o SIM support for other platforms than just for Linux. Apache has - example code (code that we could use directly pretty easily) for - other platforms. - o We should replace all short, int, long, unsigned short, unsigned int, - unsigned long with some pre-defined datatypes that really are what - we want on all platforms. int16, uint16, int32, uint32 etc. are - what we could use or maybe SilcInt16, SilcUInt16 etc. Also, boolean - datatype should be defined. +TODO/bugs In SILC Client Library +================================ - o More platform supports should be added. The code is pretty much - generic but there are some parts that require porting (SIM). Also, - some support for different platforms is needed into configure.in. + o JOIN command's argument handling is buggy. See the XXX in the code. - o SILC requires currently GCC to work because we use GCC specific - compilation options. Generally any compiler that supports inline - functions and can build shared libraries (for SIMs) should work. - These cases should be included into configure.in. + o key agreement with itself causes the packet sequence numbers go grazy. -TODO In SILC Client -=================== +TODO/bugs In SILC Server +======================== - o Implement all commands. A lot of commands are still yet to be - implemented. Most of them are trivial but some will require some - planning. Go see the command.c for unimplemented commands. + o After backup resume protocol the TOPIC_SET was not handled correctly + by all (unknown Channel ID). - o Non-blocking connection on the background must be stopped if some - other connection on same window has established. Now it is possible - that some non-blocking connection timeouts on the background when - we already have a working connection to some other place; things - goes bad. + o Channel user mode changes are notified unnecessarely when switching + to backup router on router crash. - o Finish WHOIS, finish JOIN and other commands that are partly - implemented. + o Change the server to connect to another server from low ports (706) + and not from high ports. Currently we cannot do incoming connection + checking by remote port because the port is not fixed. - o Input line on UI is buggy. Cursor movement etc bugs. Too lazy to - fix it. + o Announcements are incomplete: channel topics are not announced, + user modes (UMODE) are not announced. - o Logic for handling multiple same nicknames for example in private - message sending. I guess the logic is done in server side but is - missing from client. + o Add a timeout to handling incoming JOIN commands. It should be + enforced that JOIN command is executed only once in a second or two + seconds. Now it is possible to accept n incoming JOIN commands + and process them without any timeouts. THis must be employed because + each JOIN command will create and distribute the new channel key + to everybody on the channel. - o Private message key setting is missing and must be implemented. - Currently private messages are encrypted with session keys. This - is required by the protocol. + o Optimize the WHOIS and IDENTIFY commands to somehow check whether the + requested clients are on some channel that the server knows about. If + this is the case then the request is not needed to be forwarded to the + router. One specific optimization could be done with JOIN command. + If the previous command to the WHOIS and IDENTIFY commands are JOIN + command (from the client) it can be expected (though it must be + verified) that the client is resolving the users on the channel it just + joined. If server has done this once there is really no reason to + resolve it twice (from the router), it can reply directly back with + the information it knows. This is because the server would (will) + receive notifications from the router for users that are on a local + channel. - o Channel private key setting is missing and must be implemented. - Currently there cannot be private keys for channels. Normal channel - keys (generated by server) are used. This is required by the protocol. + The same is with whowas command. Actually with all these commands + it should be checked also whether the requested information is local. + If it is, there is no reason to send it to the router, since the server + knows it best. - o Public and private key generation is now done everytime the program - is run. Now, this is only for testing period as I've been lazy to - do it any better for now. This must be fixed. + o Add support for sending the LIST command to primary router on normal + server to receive all the created channels. Currently the command + returns only the channels the server knows about. The protocol spec + does not prohibit of sending the LIST to the router. - o I guess, public key authentication (when connecting to a server) - is not working currently. It is just matter of loading the keys - from file and using them (see corresponding code in server, it should - support public key authentication already). + o Incomplete IPv6 support: - o Multiple windows support. Basic support for multiple windows already - exists but a lot is still missing to get it working. Also, some - of the existing stuff probably needs to be tweaked a bit before the - multiple windows support could be done. And of course the actual - commands that control the windows needs to be written (/WINDDOW). + o silcd/serverid.c and its routines supports only IPv4. - o Implement /KEYMAP (or similiar) command to remap control and function - keys. + o New configuration file format must be added. The new one will be + done using the dotconf config library (lib/dotconf). The following + tasks relates closely to this as well and must be done at the same time + when adding the new config file format: - o Implement /ALIAS command to make command aliases. + o Server says that it is able to listen on multiple ports but + currently that is bogus. It can, but internals are for single + server. - o Implement /set/if/do/while etc as in IRC2. Maybe post 1.0 task. - Some scripting would be good. + o Protocol execution timeouts are hard coded, should be + configurable. - o Connection Authentication request resolving is missing and must be - done. This is required by the protocol. + o IP address fields in configuration file should accept mask + format as well, IP/MASK, and not just plain IP. - o Key Exchange protocol's responder side is missing from client. - Generally it is possible for the client to be responder so it should - be implemented (See corresponding code from server). Error handling - in the KE protocol is also in pretty bad shape in client. + o Connection classes should be actually implemented in + serverconfig.c. They can be defined but they are totally + ignored currently. And they should be redefined also. - o Configuration file loading from global and from local dirs. This - is currently missing and I guess the global is only used. Old SILC - version (in 1997) had ~./silc directory that I guess should be done - now as well. The code for handling those exists but not in current - source tree. - o Configuration file format - could be better. +TODO/bugs In SILC Libraries +=========================== - o Write help files for commands. Nice format for the help files should - be selected. I'm open for ideas. + o Optimizations to lib/silcsftp - o All allocations and freeing needs to be checked for memory leaks. - Also, return values from various allocations and functions needs to - checked. + o Do not allocate new req for every client request. Use + preallocated requests and recycle them. + o Use SilcList instead of SilcDList for requests. It is faster. -TODO In SILC Server -=================== - - o Implement all commands on server side. A lot of commands are still yet - to be implemented. Most of them are trivial but some will require some - planning. Go see the command.c for unimplemented commands. - - o DNS/IP lookup blocks the server. This must be fixed. Check the - resolver stuff (resolver(3), resolver(5)). Either we have to do the - own resolver stuff (through scheduler, if possible without writing - too much own stuff) or use threads. - - o Lenght of the packet processing timeouts needs to be checked whether - they are too short or too long. I haven't really tested whether they - are suitable. They should be tested on high load which I haven't done - at all yet. - - o Public and private key generation is now done everytime the program - is run. Now, this is only for testing period as I've been lazy to - do it any better for now. This must be fixed. - - o Server says that it is able to listen on multiple ports but currently - that is bogus. It can, but internals are for single server. - - o Command lagging must implemented. Those commands (all currently) that - has the LAG flag set they must not be allowed to be executed more than - once, say, in 2 seconds. - - o Command flag usage in general is not implemented yet. - - o Client history must be implemented. Protocol says that server must - keep history information about clients for some period of time. - - o Channel flags and user modes on channels are not implemented yet as - /MODE command is not implemented yet in client and server. - - o Protocol execution timeouts are hard coded, should be configurable. - - o Channel message sending routines uses a lot of common code. Should - create a common function for those instead of writing the same code - again everytime, as done now. - - o serverutil.c I guess should be created for util-like functions that - now resides in server.c, which is getting too big. - - o serverconfig.c and the function naming in it is inconsistent. It is - not silc_config_server* it should be silc_server_config*. As should - all the SilcConfigServer* types be SilcServerConfig*. - - o Implement DENY_CONNECTION section in serverconfig.c and in server. - - o Implement REDIRECT_CLIENT section in serverconfig.c and in server. - - o Configuration file format - could be better. - - o IP address fields in configuration file should accept mask format - as well, IP/MASK, and not just plain IP. - - o Connection classes should be actually implemented in serverconfig.c. - They can be defined but they are totally ignored currently. - - o Acceptance of incoming connections (client and server connections) - should be checked before key exchange protocol. Currently it is - checked at the authentication phase after KE, that is ok, but it should - be checked before starting KE, as well. - - o Statistics are totally missing from the server. It would be nice - to gather some statistics. - - o All allocations and freeing needs to be checked for memory leaks. - Also, return values from various allocations and functions needs to - checked. - - -TODO In SILC Libraries -====================== - - o Public key verification in SKE (SILC Key Exchange) protocol is missing, - thus currently we trust on all public keys. This probably doesn't cause - bad problems but the mechanism of verifying it from local database - (from files) needs to be done (it can open man-in-the-middle-attacks). - - o Implement PFS (Perfect Forward Secrecy) flag in SKE (and in client and - server, actually). If PFS is set, re-key must cause new key exchange. - This is required by the SILC protocol. - - o Re-key in general is actually missing (from everywhere) and must be done. - - o SKE does not send correct status types. Types are defined but not - sent. - - o Connection authentication protocol does not send correct status types. - These types are not defined currently at all. - - o PKCS#1 style RSA public key encryption/decryption/sign/verify is - missing, and should be added for interoperability reasons. The thing - I've done now is bad and should be removed as soon as possible (or - the protocol should then state the method of how they should be done). - - o SILC public key file type is bad. I'd like to see PEM encoded files. - I have public domain code for base64 encoding but it needs to be - rewritten. - - o Slow ciphers should be removed. I think we don't need more than - the AES finalists plus blowfish and RC5. - - o These slow ciphers actually don't work currently as I've tested - only the ones that are worth testing. The CBC mode on these slow - ciphers probably don't work. No need to worry, these ciphers should - be removed. - - o Scheduler needs to be analyzed on high load as it might be unfair - towards select() because it may run timeout tasks before select() and - after select(). If it is found to be unfair the timeout task running - before select() should probably be removed. - - o On select() issue; maybe we should use poll() instead if it is - available? poll() doesn't have max fd limit... - - o SIM support for SILC PKCS API needs to made so that they could be - used as SIM's. At the same time some work is required on prime - generation as the way it is done now sucks. Read from code for - more (silcpkcs.h). + o Do not allocate new buffer for every packet. Use preallocated + buffer and reallocate only if necessary. o Compression routines are missing. The protocol supports packet compression thus it must be implemented. SILC Comp API must be @@ -263,84 +132,46 @@ TODO In SILC Libraries not in distribution), but it is not used yet, and it requires some tweaking on the Makefiles (we want static lib not shared). - o Cipher API needs to be made more consistent. Some parts of the - code generated with current Cipher API looks really bad. Same - is with PKCS API, even worse actually. They need to be made - cleaner. Introducing silc_cipher_encrypt/decrypt/set_key etc. - functions (I actually don't understand why have I left these un-done). - - o Scheduler should automatically allocate task queues if NULL pointers - are passed to the silc_schedule_init. Would make initialization - cleaner. - - o Packet processing routines in client and server are actually pretty - much generic and should be moved from the client/server to the library - as generic routines (silc__packet_decrypt_rest* etc). - This requires heavy changes to the client and server. - - o Random Number Generator needs some tweaking. Reading /dev/random may - block resulting slow initialization of RNG. Some other things in the - RNG may block as well. Also, I have some pending changes to the RNG - that needs to be commited (from Schneier's Yarrow-160 paper). They - should make the RNG even better. + o All payload parsing (decoding) functions should take unsigned char * + and uint32 as data and data length as arguments. Now some of the + routines do already that but most of the routines use SilcBuffer. + The SilcBuffer ones should be removed since buf->data and buf->len + is more convenient to use. These are currently only cosmetic changes + but at some point must be done to make the payload interfaces + consistent. - o Logging should be made more generic in a way that application can - set to where the logging is destined to. Now, it is always destined - to stdout (or stderr) which is a bad thing for client. Ie. some - sort of logging registration functions or similiar should be done - (silclog.[ch] in core). The actual output of logs should be done - by callback function in the application not in lib. + o Incomplete IPv6 support: - o I don't like the ID cache system currenly implemented. Ugly and - not so good. Must be rewritten very soon. + o All network routines in lib/silcutil/silcnet.[ch] does not + support IPv6. + o silc_id_render supports only IPv4 based ID's in the file + lib/silcutil/silcutil.c. - o All allocations and freeing needs to be checked for memory leaks. + o Add builtin SOCKS and HTTP Proxy support, well the SOCKS at least. + SILC currently supports SOCKS4 and SOCKS5 but it needs to be compiled + in separately. - o There are also checks missing from allocations whether the allocation - returns valid memory or NULL. These are missing in library as well - in client and server. Either all checks has to be added or we will - have to make sure that silc_*alloc()s always return valid memory - and assert()s if the system's memory allocator (*alloc()) fails. - o silc_buffer_[un]format() needs to be made more stable as it may - crash the SILC if malformed data is sent as argument. There are a - lot of places in client and server where we trust directly data coming - from network and try to unformat it. The unformatting routine needs - to be able handle situations where data sent is malformed, by mistake - or intentionally. This is important as it is easy to crash the SILC - now by just sending malformed data. Also, in client and server we - must start checking the return value from silc_buffer_[un]format. +TODO/Bugs in native WIN32 support (libraries) +============================================= + o silc_net_create_connection_async does not work the same way than on + Unix. Do it with threads on WIN32. The function works but is not + actually async currently. -Other Things TODO -================= - o Write manuals for server. +TODO In SILC Protocol +===================== - o Write manuals for client. - - o Write SILC Library Reference manual. This would include all the SILC - API's with simple examples how the functions are to be used. This is - pretty easy to create by taking all the functions plus their comments - from source/header files. However, same sort of reference manual - should be written for client and server as well. + o If channel founder mode is set and the invite mode is set on channel + then the founder should be added to the list automatically so that + if the founder signoff's it will be able join again to the invite only + channel wihtout being invited. TODO After 1.0 ============== - o Pthreads support. A lot of problems are solved with server (and with - client as well) if we add pthread support. We can forget things such - as non-blocking connecting etc, and we can do things such as DNS/IP - lookups async. The server itself also benefits great deal from - threads, especially from performance point of view. - - But, this is not a small task and almost entire SILC Library has to - be made re-entrant. Own API is probably added for the threads support - to make changes in the future as painless as possible. So the API - would have things like silc_mutex_lock, silc_mutex_unlock and - friends... - o X.509 certificate support. SILC protocol supports certificates and it would be great to have support for them. This is a big task as support has to be made for ASN.1 as well. I've looked into OpenSSL @@ -356,14 +187,10 @@ TODO After 1.0 to start writing one myself. Anyhow, the OpenSSL X.509 lib should be checked. + Other package that should be checked is the NSS's X509 library. + o SSH2 public keys support. Maybe - not really needed but could be nice as SSH is widely used all over the place. SILC Protocol supports SSH2 public keys. - o IRC support for SILC client. This would be nice to have on client - as it could be used to connect to SILC and IRC. People wouldn't - have to have two different clients when same would work on both. - I'd like to see this done as SIM, after the extended SIM support - has been added to SILC. - o Cipher optimizations (asm, that this) at least for i386 would be nice. diff --git a/acconfig.h b/acconfig.h deleted file mode 100644 index 65e1903e..00000000 --- a/acconfig.h +++ /dev/null @@ -1,16 +0,0 @@ -/* Name of the package. */ -#undef PACKAGE - -/* Version of the package. */ -#undef VERSION - -/* Debugging */ -#undef SILC_DEBUG - -/* Default configuration file */ -#undef SILC_SERVER_CONFIG_FILE - -/* SIM (SILC Module) support */ -#undef SILC_SIM -#undef HAVE_RTLD_NOW -#undef HAVE_RTLD_LAZY diff --git a/acconfig.h.pre b/acconfig.h.pre new file mode 100644 index 00000000..a1649da8 --- /dev/null +++ b/acconfig.h.pre @@ -0,0 +1,82 @@ +/* Name of the package. */ +#undef PACKAGE + +/* Version of the package. */ +#undef VERSION + +/* Debugging */ +#undef SILC_DEBUG + +/* Default configuration file */ +#undef SILC_SERVER_CONFIG_FILE + +/* Default pid file */ +#undef SILC_SERVER_PID_FILE + +/* Multi-thread support */ +#undef SILC_THREADS +#undef SILC_HAVE_PTHREAD + +/* Default paths */ +#undef SILC_ETCDIR +#undef SILC_HELPDIR +#undef SILC_DOCDIR +#undef SILC_MODULESDIR +#undef SILC_LOGSDIR + +/* SIM (SILC Module) support */ +#undef SILC_SIM +#undef HAVE_RTLD_NOW +#undef HAVE_RTLD_LAZY + +/* Types */ +#undef SILC_SIZEOF_LONG_LONG +#undef SILC_SIZEOF_LONG +#undef SILC_SIZEOF_INT +#undef SILC_SIZEOF_SHORT +#undef SILC_SIZEOF_CHAR +#undef SILC_SIZEOF_VOID_P + +/* MP library */ +#undef SILC_MP_GMP +#undef SILC_MP_NSS_MPI + +/* Redefs for SOCKS5 library */ +/* macros/curses checks */ +#undef HAS_CURSES +#undef USE_SUNOS_CURSES +#undef USE_BSD_CURSES +#undef USE_SYSV_CURSES +#undef USE_NCURSES +#undef NO_COLOR_CURSES +#undef SCO_FLAVOR + +#undef SOCKS +#undef SOCKS5 +#undef Rconnect +#undef Rgetsockname +#undef Rgetpeername +#undef Rbind +#undef Raccept +#undef Rlisten +#undef Rselect +#undef Rrecvfrom +#undef Rsendto +#undef Rrecv +#undef Rsend +#undef Rread +#undef Rwrite +#undef Rrresvport +#undef Rshutdown +#undef Rlisten +#undef Rclose +#undef Rdup +#undef Rdup2 +#undef Rfclose +#undef Rgethostbyname + +/* Native WIN32 compilation (-mno-cygwin GCC option) under cygwin, though + the code compiles with any native WIN32 compiler. */ +#undef SILC_WIN32 + +/* SILC distribution definitions (leave this at the end of file) */ diff --git a/apps/irssi/COPYING b/apps/irssi/COPYING new file mode 100644 index 00000000..d60c31a9 --- /dev/null +++ b/apps/irssi/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/apps/irssi/Makefile.am b/apps/irssi/Makefile.am index 304c44d8..a8a1eff4 100644 --- a/apps/irssi/Makefile.am +++ b/apps/irssi/Makefile.am @@ -8,21 +8,24 @@ default-theme.h: $(srcdir)/default.theme SUBDIRS = src docs -confdir = $(sysconfdir)/irssi +include $(top_srcdir)/Makefile.defines.in + +#confdir = $(sysconfdir)/irssi +confdir = $(silc_etcdir) conf_DATA = config default.theme noinst_HEADERS = irssi-version.h EXTRA_DIST = \ autogen.sh \ - curses.m4 \ README \ file2header.sh \ irssi.spec \ irssi.spec.in \ $(conf_DATA) \ irssi-config.in \ - irssi-icon.png + Makefile.defines.in \ + Makefile.defines_int.in ## make rpms rpm: Makefile diff --git a/apps/irssi/autogen.sh b/apps/irssi/autogen.sh index daf8a990..d968658d 100755 --- a/apps/irssi/autogen.sh +++ b/apps/irssi/autogen.sh @@ -88,9 +88,9 @@ esac rm -f aclocal.m4 if grep "^AM_PROG_LIBTOOL" configure.in >/dev/null; then echo "Running libtoolize..." - libtoolize --force --copy + libtoolize --copy --force fi -aclocalinclude="$ACLOCAL_FLAGS -I ." +aclocalinclude="$ACLOCAL_FLAGS" echo "Running aclocal $aclocalinclude ..." aclocal $aclocalinclude if grep "^AM_CONFIG_HEADER" configure.in >/dev/null; then @@ -99,7 +99,5 @@ if grep "^AM_CONFIG_HEADER" configure.in >/dev/null; then fi echo "Running autoconf ..." autoconf -echo "Running automake --gnu $am_opt ..." -automake --add-missing --gnu $am_opt - -conf_flags="--enable-maintainer-mode --enable-compile-warnings" #--enable-iso-c +echo "Running automake $am_opt ..." +automake --add-missing --foreign $am_opt diff --git a/apps/irssi/config b/apps/irssi/config index 876b4020..b930de3d 100644 --- a/apps/irssi/config +++ b/apps/irssi/config @@ -1,33 +1,18 @@ lservers = ( - { address = "irc.stealth.net"; chatnet = IRCNet; port = 6668; }, - { address = "irc.efnet.net"; chatnet = EFNet; port = 6667; }, - { address = "irc.undernet.org"; chatnet = Undernet; port = 6667; }, - { address = "irc.dal.net"; chatnet = DALnet; port = 6667; }, - { address = "irc.openprojects.net"; chatnet = OPN; port = 6667; }, - { address = "irc.ptlink.net"; chatnet = PTlink; port = 6667; } - { address = "silc.pspt.fi"; chatnet = SILC; port = 706; } + { address = "silc.silcnet.org"; chatnet = SILCNet; port = 706; } ); chatnets = { - IRCNet = { type = "IRC"; max_kicks = 4; max_modes = 3; max_msgs = 5; max_whois = 4; }; - EFNet = { type = "IRC"; max_kicks = 4; max_modes = 4; max_msgs = 3; }; - Undernet = { type = "IRC"; max_kicks = 4; max_modes = 3; max_msgs = 3; max_query_chans = "1"; }; - DALNet = { type = "IRC"; max_kicks = 4; max_modes = 6; max_msgs = 3; }; - OPN = { type = "IRC"; max_kicks = 1; max_modes = 6; max_msgs = 100; }; - PTLink = { type = "IRC"; max_kicks = 1; max_modes = 6; max_msgs = 100; }; - SILC = { type = "SILC"; }; + SILCNet = { type = "SILC"; }; }; channels = ( - { name = "#irssi"; chatnet = ircnet; autojoin = No; }, - { name = "#irssi"; chatnet = opn; autojoin = No; }, - { name = "#silc"; chatnet = silc; autojoin = No; } + { name = "#silc"; chatnet = silcnet; autojoin = No; } ); aliases = { - J = "join"; - WJOIN = "join -window"; - WQUERY = "query -window"; + JOIN = "join -window"; + QUERY = "query -window"; LEAVE = "part"; BYE = "quit"; EXIT = "quit"; @@ -37,6 +22,7 @@ aliases = { HOST = "userhost"; LAST = "lastlog"; SAY = "msg *"; + WHO = "users *"; WI = "whois"; WII = "whois $0 $0"; WW = "whowas"; @@ -56,10 +42,22 @@ aliases = { IG = "ignore"; UNIG = "unignore"; SB = "scrollback"; - UMODE = "mode $N"; WC = "window close"; WN = "window new hide"; - SV = "say Irssi $J - http://irssi.org/"; GOTO = "sb goto"; CHAT = "dcc chat"; + ADMIN = "info"; +}; + +settings = { + "fe-common/core" = { + autocreate_own_query = "no"; + use_status_window = "no"; + autoclose_windows = "no"; + use_msgs_window = "no"; + autocreate_windows = "no"; + autocreate_query_level = "none"; + use_auto_addr = "no"; + }; + "fe-text" = { topicbar = "no"; mail_counter = "yes"; indent = "8"; }; }; diff --git a/apps/irssi/config.h.in b/apps/irssi/config.h.in deleted file mode 100644 index 14b09153..00000000 --- a/apps/irssi/config.h.in +++ /dev/null @@ -1,77 +0,0 @@ -/* config.h.in. Generated automatically from configure.in by autoheader. */ - -/* Define if you need to in order for stat and other things to work. */ -#undef _POSIX_SOURCE - -/* Define if you have the ANSI C header files. */ -#undef STDC_HEADERS - -/* misc.. */ -#undef MEM_DEBUG -#undef HAVE_IPV6 -#undef HAVE_POPT_H -#undef HAVE_SOCKS_H -#undef HAVE_PL_PERL -#undef HAVE_STATIC_PERL -#undef HAVE_GMODULE -#undef WANT_BIG5 - -/* macros/curses checks */ -#undef HAS_CURSES -#undef USE_SUNOS_CURSES -#undef USE_BSD_CURSES -#undef USE_SYSV_CURSES -#undef USE_NCURSES -#undef NO_COLOR_CURSES -#undef SCO_FLAVOR - -/* our own curses checks */ -#undef USE_CURSES_WINDOWS -#undef HAVE_NCURSES_USE_DEFAULT_COLORS -#undef HAVE_CURSES_IDCOK -#undef HAVE_CURSES_RESIZETERM -#undef HAVE_CURSES_WRESIZE -#undef USE_CURSES_WINDOWS - -/* Define if you have the fcntl function. */ -#undef HAVE_FCNTL - -/* Define if you have the mkfifo function. */ -#undef HAVE_MKFIFO - -/* Define if you have the header file. */ -#undef HAVE_DIRENT_H - -/* Define if you have the header file. */ -#undef HAVE_LIBINTL_H - -/* Define if you have the header file. */ -#undef HAVE_REGEX_H - -/* Define if you have the header file. */ -#undef HAVE_STDLIB_H - -/* Define if you have the header file. */ -#undef HAVE_STRING_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_IOCTL_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_TIME_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_UTSNAME_H - -/* Define if you have the header file. */ -#undef HAVE_UNISTD_H - -/* Name of package */ -#undef PACKAGE - -/* Version number of package */ -#undef VERSION - -/* Define to 'int' if doesn't define. */ -#undef socklen_t - diff --git a/apps/irssi/configure.in b/apps/irssi/configure.in index 87063b4e..dfc21b47 100644 --- a/apps/irssi/configure.in +++ b/apps/irssi/configure.in @@ -1,7 +1,7 @@ AC_INIT(src) AM_CONFIG_HEADER(config.h) -AM_INIT_AUTOMAKE(Irssi SILC, 0.7.98.3) +AM_INIT_AUTOMAKE(Irssi-SILC, 0.7.98.3) AM_MAINTAINER_MODE @@ -10,7 +10,7 @@ AC_PROG_CC AC_PROG_CPP AC_STDC_HEADERS AC_ARG_PROGRAM -AM_PROG_LIBTOOL +AC_PROG_RANLIB dnl * ahem.. :) we don't want static libraries for modules if test "x$lt_target" = "x"; then @@ -25,12 +25,7 @@ if test "x$enable_static" = "xno"; then AC_ERROR([Don't give --disable-static option to configure]) fi -${CONFIG_SHELL-/bin/sh} $ac_aux_dir/ltconfig --no-reexec \ -$libtool_flags --disable-static --output=libtool-shared --no-verify $ac_aux_dir/ltmain.sh $lt_target \ -|| { echo "configure: error: libtool configure failed" 1>&2; exit 1; } -${CONFIG_SHELL-/bin/sh} $ac_aux_dir/ltconfig --no-reexec \ -$libtool_flags --no-verify $ac_aux_dir/ltmain.sh $host \ -|| { echo "configure: error: libtool configure failed" 1>&2; exit 1; } +AC_CONFIG_AUX_DIR(.) AC_CHECK_HEADERS(string.h stdlib.h unistd.h dirent.h sys/ioctl.h libintl.h) @@ -128,7 +123,7 @@ AC_ARG_ENABLE(perl-path, perl_lib_dir_given=yes fi fi, - want_perl=yes) + want_perl=no) AC_ARG_ENABLE(perl, [ --enable-perl[=yes|no|static] Build with Perl support - also specifies @@ -141,7 +136,7 @@ AC_ARG_ENABLE(perl, else want_perl=no fi, - want_perl=yes) + want_perl=no) AC_ARG_WITH(tests, [ --with-tests Run all the tests], @@ -352,64 +347,302 @@ fi PROG_LIBS="$PROG_LIBS $GLIB_LIBS" dnl ** -dnl ** check if we can link dynamic libraries to modules -dnl ** also checks if libraries are built to .libs dir +dnl ** curses checks dnl ** -AC_MSG_CHECKING([if we can link dynamic libraries with modules]) -DYNLIB_MODULES=no +dnl Curses detection: Munged from Midnight Commander's configure.in +dnl +dnl What it does: +dnl ============= +dnl +dnl - Determine which version of curses is installed on your system +dnl and set the -I/-L/-l compiler entries and add a few preprocessor +dnl symbols +dnl - Do an AC_SUBST on the CURSES_INCLUDEDIR and CURSES_LIBS so that +dnl @CURSES_INCLUDEDIR@ and @CURSES_LIBS@ will be available in +dnl Makefile.in's +dnl - Modify the following configure variables (these are the only +dnl curses.m4 variables you can access from within configure.in) +dnl CURSES_INCLUDEDIR - contains -I's and possibly -DRENAMED_CURSES if +dnl an ncurses.h that's been renamed to curses.h +dnl is found. +dnl CURSES_LIBS - sets -L and -l's appropriately +dnl CFLAGS - if --with-sco, add -D_SVID3 +dnl has_curses - exports result of tests to rest of configure +dnl +dnl Usage: +dnl ====== +dnl 1) Add lines indicated below to acconfig.h +dnl 2) call AC_CHECK_CURSES after AC_PROG_CC in your configure.in +dnl 3) Instead of #include you should use the following to +dnl properly locate ncurses or curses header file +dnl +dnl #if defined(USE_NCURSES) && !defined(RENAMED_NCURSES) +dnl #include +dnl #else +dnl #include +dnl #endif +dnl +dnl 4) Make sure to add @CURSES_INCLUDEDIR@ to your preprocessor flags +dnl 5) Make sure to add @CURSES_LIBS@ to your linker flags or LIBS +dnl +dnl Notes with automake: +dnl - call AM_CONDITIONAL(HAS_CURSES, test "$has_curses" = true) from +dnl configure.in +dnl - your Makefile.am can look something like this +dnl ----------------------------------------------- +dnl INCLUDES= blah blah blah $(CURSES_INCLUDEDIR) +dnl if HAS_CURSES +dnl CURSES_TARGETS=name_of_curses_prog +dnl endif +dnl bin_PROGRAMS = other_programs $(CURSES_TARGETS) +dnl other_programs_SOURCES = blah blah blah +dnl name_of_curses_prog_SOURCES = blah blah blah +dnl other_programs_LDADD = blah +dnl name_of_curses_prog_LDADD = blah $(CURSES_LIBS) +dnl ----------------------------------------------- +dnl +dnl +dnl The following lines should be added to acconfig.h: +dnl ================================================== +dnl +dnl /*=== Curses version detection defines ===*/ +dnl /* Found some version of curses that we're going to use */ +dnl #undef HAS_CURSES +dnl +dnl /* Use SunOS SysV curses? */ +dnl #undef USE_SUNOS_CURSES +dnl +dnl /* Use old BSD curses - not used right now */ +dnl #undef USE_BSD_CURSES +dnl +dnl /* Use SystemV curses? */ +dnl #undef USE_SYSV_CURSES +dnl +dnl /* Use Ncurses? */ +dnl #undef USE_NCURSES +dnl +dnl /* If you Curses does not have color define this one */ +dnl #undef NO_COLOR_CURSES +dnl +dnl /* Define if you want to turn on SCO-specific code */ +dnl #undef SCO_FLAVOR +dnl +dnl /* Set to reflect version of ncurses * +dnl * 0 = version 1.* +dnl * 1 = version 1.9.9g +dnl * 2 = version 4.0/4.1 */ +dnl #undef NCURSES_970530 +dnl +dnl /*=== End new stuff for acconfig.h ===*/ +dnl + + +AC_DEFUN(AC_CHECK_CURSES,[ + search_ncurses=true + screen_manager="" + has_curses=false -dnl ** compile object file -cat > conftest.c < -int modfunc(void){return (int)floor(1.2);} -EOF + CFLAGS=${CFLAGS--O} -./libtool --mode=compile $CC $CFLAGS -c conftest.c 2> /dev/null > /dev/null -if test ! -s conftest.lo; then - AC_ERROR([error compiling test module]) -fi + AC_SUBST(CURSES_LIBS) + AC_SUBST(CURSES_INCLUDEDIR) -dnl ** link to library -./libtool --mode=link $CC $CFLAGS $LDFLAGS -rpath /usr/lib conftest.lo -lm -o libconftest.la > /dev/null -if test ! -s .libs/libconftest.a; then - AC_ERROR([error, can't even find .a library]) -fi + AC_ARG_WITH(sco, + [ --with-sco Use this to turn on SCO-specific code],[ + if test x$withval = xyes; then + AC_DEFINE(SCO_FLAVOR) + CFLAGS="$CFLAGS -D_SVID3" + fi + ]) -dnl ** check if dynamic linking worked -libfile=`grep '^library_names' libconftest.la|$sedpath "s/library_names='\(.*\)'.*/\1/"|$sedpath 's/.* \([[^ ]]*\)$/\1/'` -if test ! -s .libs/$libfile; then - AC_MSG_RESULT([no, error linking test module]) -else - cat > conftest.c < -main() { -GModule *m; int (*modfunc)(void); -m = g_module_open(".libs/$libfile", 0); -if (!m) g_print("error loading: %s", g_module_error()); -else if (!g_module_symbol(m, "modfunc", (gpointer *) &modfunc)) - g_print("modfunc() symbol not found from module"); -else if (modfunc() == 1) g_print("ok"); else g_print("wrong result?! 1 vs %d", modfunc()); -return 0; } -EOF - $CC $CFLAGS conftest.c -o conftest $GLIB_CFLAGS $GLIB_LIBS 2> /dev/null > /dev/null - if test ! -s conftest; then - AC_MSG_RESULT([no, error compiling test program]) - else - status="`./conftest`" - if test "x$status" = "xok"; then - DYNLIB_MODULES=yes - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no, error running: $status]) + AC_ARG_WITH(sunos-curses, + [ --with-sunos-curses Used to force SunOS 4.x curses],[ + if test x$withval = xyes; then + AC_USE_SUNOS_CURSES + fi + ]) + + AC_ARG_WITH(osf1-curses, + [ --with-osf1-curses Used to force OSF/1 curses],[ + if test x$withval = xyes; then + AC_USE_OSF1_CURSES + fi + ]) + + AC_ARG_WITH(vcurses, + [ --with-vcurses[=incdir] Used to force SysV curses], + if test x$withval != xyes; then + CURSES_INCLUDEDIR="-I$withval" + fi + AC_USE_SYSV_CURSES + ) + + AC_ARG_WITH(ncurses, + [ --with-ncurses[=dir] Compile with ncurses/locate base dir], + if test x$withval = xno ; then + search_ncurses=false + elif test x$withval != xyes ; then + AC_NCURSES($withval/include, ncurses.h, -L$withval/lib -lncurses, -I$withval/include, "ncurses on $withval/include") + fi + ) + + if $search_ncurses + then + AC_SEARCH_NCURSES() + fi +]) + + +AC_DEFUN(AC_USE_SUNOS_CURSES, [ + search_ncurses=false + screen_manager="SunOS 4.x /usr/5include curses" + AC_MSG_RESULT(Using SunOS 4.x /usr/5include curses) + AC_DEFINE(USE_SUNOS_CURSES) + AC_DEFINE(HAS_CURSES) + has_curses=true + AC_DEFINE(NO_COLOR_CURSES) + AC_DEFINE(USE_SYSV_CURSES) + CURSES_INCLUDEDIR="-I/usr/5include" + CURSES_LIBS="/usr/5lib/libcurses.a /usr/5lib/libtermcap.a" + AC_MSG_RESULT(Please note that some screen refreshs may fail) +]) + +AC_DEFUN(AC_USE_OSF1_CURSES, [ + AC_MSG_RESULT(Using OSF1 curses) + search_ncurses=false + screen_manager="OSF1 curses" + AC_DEFINE(HAS_CURSES) + has_curses=true + AC_DEFINE(NO_COLOR_CURSES) + AC_DEFINE(USE_SYSV_CURSES) + CURSES_LIBS="-lcurses" +]) + +AC_DEFUN(AC_USE_SYSV_CURSES, [ + AC_MSG_RESULT(Using SysV curses) + AC_DEFINE(HAS_CURSES) + has_curses=true + AC_DEFINE(USE_SYSV_CURSES) + search_ncurses=false + screen_manager="SysV/curses" + CURSES_LIBS="-lcurses" +]) + +dnl AC_ARG_WITH(bsd-curses, +dnl [--with-bsd-curses Used to compile with bsd curses, not very fancy], +dnl search_ncurses=false +dnl screen_manager="Ultrix/cursesX" +dnl if test $system = ULTRIX +dnl then +dnl THIS_CURSES=cursesX +dnl else +dnl THIS_CURSES=curses +dnl fi +dnl +dnl CURSES_LIBS="-l$THIS_CURSES -ltermcap" +dnl AC_DEFINE(HAS_CURSES) +dnl has_curses=true +dnl AC_DEFINE(USE_BSD_CURSES) +dnl AC_MSG_RESULT(Please note that some screen refreshs may fail) +dnl AC_WARN(Use of the bsdcurses extension has some) +dnl AC_WARN(display/input problems.) +dnl AC_WARN(Reconsider using xcurses) +dnl) + + +dnl +dnl Parameters: directory filename cureses_LIBS curses_INCLUDEDIR nicename +dnl +AC_DEFUN(AC_NCURSES, [ + if $search_ncurses + then + if test -f $1/$2 + then + AC_MSG_RESULT(Found ncurses on $1/$2) + + CURSES_LIBS="$3" + AC_CHECK_LIB(ncurses, initscr, , [ + CHECKLIBS=`echo "$3"|sed 's/-lncurses/-lcurses/g'` + AC_CHECK_LIB(curses, initscr, [ + CURSES_LIBS="$CHECKLIBS" + ],, $CHECKLIBS) + ], $CURSES_LIBS) + CURSES_INCLUDEDIR="$4" + search_ncurses=false + screen_manager=$5 + AC_DEFINE(HAS_CURSES) + has_curses=true + has_ncurses=true + AC_DEFINE(USE_NCURSES) + fi fi - fi -fi -rm -rf conftest conftest.* libconftest.* .libs +]) -dnl ** -dnl ** curses checks -dnl ** +AC_DEFUN(AC_SEARCH_NCURSES, [ + AC_CHECKING("location of ncurses.h file") + + AC_NCURSES(/usr/include, ncurses.h, -lncurses,, "ncurses on /usr/include") + AC_NCURSES(/usr/include/ncurses, ncurses.h, -lncurses, -I/usr/include/ncurses, "ncurses on /usr/include/ncurses") + AC_NCURSES(/usr/local/include, ncurses.h, -L/usr/local/lib -lncurses, -I/usr/local/include, "ncurses on /usr/local") + AC_NCURSES(/usr/pkg/include, ncurses.h, -L/usr/pkg/lib -lncurses, -I/usr/pkg/include, "ncurses on /usr/pkg") + AC_NCURSES(/usr/contrib/include, ncurses.h, -L/usr/contrib/lib -lncurses, -I/usr/contrib/include, "ncurses on /usr/contrib") + AC_NCURSES(/usr/local/include/ncurses, ncurses.h, -L/usr/local/lib -L/usr/local/lib/ncurses -lncurses, -I/usr/local/include/ncurses, "ncurses on /usr/local/include/ncurses") + + AC_NCURSES(/usr/local/include/ncurses, curses.h, -L/usr/local/lib -lncurses, -I/usr/local/include/ncurses -DRENAMED_NCURSES, "renamed ncurses on /usr/local/.../ncurses") + + AC_NCURSES(/usr/include/ncurses, curses.h, -lncurses, -I/usr/include/ncurses -DRENAMED_NCURSES, "renamed ncurses on /usr/include/ncurses") + + dnl + dnl We couldn't find ncurses, try SysV curses + dnl + if $search_ncurses + then + AC_EGREP_HEADER(init_color, /usr/include/curses.h, + AC_USE_SYSV_CURSES) + AC_EGREP_CPP(USE_NCURSES,[ +#include +#ifdef __NCURSES_H +#undef USE_NCURSES +USE_NCURSES +#endif +],[ + CURSES_INCLUDEDIR="$CURSES_INCLUDEDIR -DRENAMED_NCURSES" + AC_DEFINE(HAS_CURSES) + has_curses=true + has_ncurses=true + AC_DEFINE(USE_NCURSES) + search_ncurses=false + screen_manager="ncurses installed as curses" +]) + fi + + dnl + dnl Try SunOS 4.x /usr/5{lib,include} ncurses + dnl The flags USE_SUNOS_CURSES, USE_BSD_CURSES and BUGGY_CURSES + dnl should be replaced by a more fine grained selection routine + dnl + if $search_ncurses + then + if test -f /usr/5include/curses.h + then + AC_USE_SUNOS_CURSES + fi + fi + + dnl use whatever curses there happens to be + if $search_ncurses + then + if test -f /usr/include/curses.h + then + CURSES_LIBS="-lcurses" + AC_DEFINE(HAS_CURSES) + has_curses=true + search_ncurses=false + screen_manager="curses" + fi + fi +]) if test "x$want_textui" = "xyes"; then AC_CHECK_CURSES @@ -647,12 +880,13 @@ for c in $CHAT_MODULES; do file="$srcdir/src/$c/$c.c" echo "/* this file is automatically generated by configure - don't change */" > $file - echo "void ${c}_core_init(void); void ${c}_core_deinit(void);" >> $file + echo "void ${c}_core_init(void); void ${c}_core_init_finish(void); void ${c}_core_deinit(void);" >> $file if test "x$module_inits" != "x"; then echo "$module_inits" | $sedpath -e 's/()/(void)/g' -e 's/ /void /g' >> $file echo "$module_deinits" | $sedpath -e 's/ *$//' -e 's/()/(void)/g' -e 's/ /void /g' -e 's/^/void /' >> $file fi echo "void ${c}_init(void) { ${c}_core_init(); $module_inits }" >> $file + echo "void ${c}_init_finish(void) { ${c}_core_init_finish(); $module_inits }" >> $file echo "void ${c}_deinit(void) { $module_deinits ${c}_core_deinit(); }" >> $file if test -f $srcdir/src/fe-common/$c/module.h; then @@ -700,6 +934,9 @@ if test "x$want_ipv6" = "xyes"; then AC_DEFINE(HAVE_IPV6) fi +INCLUDE_DEFINES_INT="include \$(top_srcdir)/Makefile.defines_int" +AC_SUBST(INCLUDE_DEFINES_INT) + AC_OUTPUT( Makefile src/Makefile @@ -781,4 +1018,3 @@ if test "x$want_perl" = "xyes"; then fi fi echo "Install prefix ............. : $prefix" - diff --git a/apps/irssi/curses.m4 b/apps/irssi/curses.m4 deleted file mode 100644 index 7865b1cb..00000000 --- a/apps/irssi/curses.m4 +++ /dev/null @@ -1,295 +0,0 @@ -dnl Curses detection: Munged from Midnight Commander's configure.in -dnl -dnl What it does: -dnl ============= -dnl -dnl - Determine which version of curses is installed on your system -dnl and set the -I/-L/-l compiler entries and add a few preprocessor -dnl symbols -dnl - Do an AC_SUBST on the CURSES_INCLUDEDIR and CURSES_LIBS so that -dnl @CURSES_INCLUDEDIR@ and @CURSES_LIBS@ will be available in -dnl Makefile.in's -dnl - Modify the following configure variables (these are the only -dnl curses.m4 variables you can access from within configure.in) -dnl CURSES_INCLUDEDIR - contains -I's and possibly -DRENAMED_CURSES if -dnl an ncurses.h that's been renamed to curses.h -dnl is found. -dnl CURSES_LIBS - sets -L and -l's appropriately -dnl CFLAGS - if --with-sco, add -D_SVID3 -dnl has_curses - exports result of tests to rest of configure -dnl -dnl Usage: -dnl ====== -dnl 1) Add lines indicated below to acconfig.h -dnl 2) call AC_CHECK_CURSES after AC_PROG_CC in your configure.in -dnl 3) Instead of #include you should use the following to -dnl properly locate ncurses or curses header file -dnl -dnl #if defined(USE_NCURSES) && !defined(RENAMED_NCURSES) -dnl #include -dnl #else -dnl #include -dnl #endif -dnl -dnl 4) Make sure to add @CURSES_INCLUDEDIR@ to your preprocessor flags -dnl 5) Make sure to add @CURSES_LIBS@ to your linker flags or LIBS -dnl -dnl Notes with automake: -dnl - call AM_CONDITIONAL(HAS_CURSES, test "$has_curses" = true) from -dnl configure.in -dnl - your Makefile.am can look something like this -dnl ----------------------------------------------- -dnl INCLUDES= blah blah blah $(CURSES_INCLUDEDIR) -dnl if HAS_CURSES -dnl CURSES_TARGETS=name_of_curses_prog -dnl endif -dnl bin_PROGRAMS = other_programs $(CURSES_TARGETS) -dnl other_programs_SOURCES = blah blah blah -dnl name_of_curses_prog_SOURCES = blah blah blah -dnl other_programs_LDADD = blah -dnl name_of_curses_prog_LDADD = blah $(CURSES_LIBS) -dnl ----------------------------------------------- -dnl -dnl -dnl The following lines should be added to acconfig.h: -dnl ================================================== -dnl -dnl /*=== Curses version detection defines ===*/ -dnl /* Found some version of curses that we're going to use */ -dnl #undef HAS_CURSES -dnl -dnl /* Use SunOS SysV curses? */ -dnl #undef USE_SUNOS_CURSES -dnl -dnl /* Use old BSD curses - not used right now */ -dnl #undef USE_BSD_CURSES -dnl -dnl /* Use SystemV curses? */ -dnl #undef USE_SYSV_CURSES -dnl -dnl /* Use Ncurses? */ -dnl #undef USE_NCURSES -dnl -dnl /* If you Curses does not have color define this one */ -dnl #undef NO_COLOR_CURSES -dnl -dnl /* Define if you want to turn on SCO-specific code */ -dnl #undef SCO_FLAVOR -dnl -dnl /* Set to reflect version of ncurses * -dnl * 0 = version 1.* -dnl * 1 = version 1.9.9g -dnl * 2 = version 4.0/4.1 */ -dnl #undef NCURSES_970530 -dnl -dnl /*=== End new stuff for acconfig.h ===*/ -dnl - - -AC_DEFUN(AC_CHECK_CURSES,[ - search_ncurses=true - screen_manager="" - has_curses=false - - CFLAGS=${CFLAGS--O} - - AC_SUBST(CURSES_LIBS) - AC_SUBST(CURSES_INCLUDEDIR) - - AC_ARG_WITH(sco, - [ --with-sco Use this to turn on SCO-specific code],[ - if test x$withval = xyes; then - AC_DEFINE(SCO_FLAVOR) - CFLAGS="$CFLAGS -D_SVID3" - fi - ]) - - AC_ARG_WITH(sunos-curses, - [ --with-sunos-curses Used to force SunOS 4.x curses],[ - if test x$withval = xyes; then - AC_USE_SUNOS_CURSES - fi - ]) - - AC_ARG_WITH(osf1-curses, - [ --with-osf1-curses Used to force OSF/1 curses],[ - if test x$withval = xyes; then - AC_USE_OSF1_CURSES - fi - ]) - - AC_ARG_WITH(vcurses, - [ --with-vcurses[=incdir] Used to force SysV curses], - if test x$withval != xyes; then - CURSES_INCLUDEDIR="-I$withval" - fi - AC_USE_SYSV_CURSES - ) - - AC_ARG_WITH(ncurses, - [ --with-ncurses[=dir] Compile with ncurses/locate base dir], - if test x$withval = xno ; then - search_ncurses=false - elif test x$withval != xyes ; then - AC_NCURSES($withval/include, ncurses.h, -L$withval/lib -lncurses, -I$withval/include, "ncurses on $withval/include") - fi - ) - - if $search_ncurses - then - AC_SEARCH_NCURSES() - fi -]) - - -AC_DEFUN(AC_USE_SUNOS_CURSES, [ - search_ncurses=false - screen_manager="SunOS 4.x /usr/5include curses" - AC_MSG_RESULT(Using SunOS 4.x /usr/5include curses) - AC_DEFINE(USE_SUNOS_CURSES) - AC_DEFINE(HAS_CURSES) - has_curses=true - AC_DEFINE(NO_COLOR_CURSES) - AC_DEFINE(USE_SYSV_CURSES) - CURSES_INCLUDEDIR="-I/usr/5include" - CURSES_LIBS="/usr/5lib/libcurses.a /usr/5lib/libtermcap.a" - AC_MSG_RESULT(Please note that some screen refreshs may fail) -]) - -AC_DEFUN(AC_USE_OSF1_CURSES, [ - AC_MSG_RESULT(Using OSF1 curses) - search_ncurses=false - screen_manager="OSF1 curses" - AC_DEFINE(HAS_CURSES) - has_curses=true - AC_DEFINE(NO_COLOR_CURSES) - AC_DEFINE(USE_SYSV_CURSES) - CURSES_LIBS="-lcurses" -]) - -AC_DEFUN(AC_USE_SYSV_CURSES, [ - AC_MSG_RESULT(Using SysV curses) - AC_DEFINE(HAS_CURSES) - has_curses=true - AC_DEFINE(USE_SYSV_CURSES) - search_ncurses=false - screen_manager="SysV/curses" - CURSES_LIBS="-lcurses" -]) - -dnl AC_ARG_WITH(bsd-curses, -dnl [--with-bsd-curses Used to compile with bsd curses, not very fancy], -dnl search_ncurses=false -dnl screen_manager="Ultrix/cursesX" -dnl if test $system = ULTRIX -dnl then -dnl THIS_CURSES=cursesX -dnl else -dnl THIS_CURSES=curses -dnl fi -dnl -dnl CURSES_LIBS="-l$THIS_CURSES -ltermcap" -dnl AC_DEFINE(HAS_CURSES) -dnl has_curses=true -dnl AC_DEFINE(USE_BSD_CURSES) -dnl AC_MSG_RESULT(Please note that some screen refreshs may fail) -dnl AC_WARN(Use of the bsdcurses extension has some) -dnl AC_WARN(display/input problems.) -dnl AC_WARN(Reconsider using xcurses) -dnl) - - -dnl -dnl Parameters: directory filename cureses_LIBS curses_INCLUDEDIR nicename -dnl -AC_DEFUN(AC_NCURSES, [ - if $search_ncurses - then - if test -f $1/$2 - then - AC_MSG_RESULT(Found ncurses on $1/$2) - - CURSES_LIBS="$3" - AC_CHECK_LIB(ncurses, initscr, [ - ], [ - CHECKLIBS=`echo "$3"|sed 's/-lncurses/-lcurses/g'` - AC_CHECK_LIB(curses, initscr, [ - CURSES_LIBS="$CHECKLIBS" - ],, $CHECKLIBS) - ], $CURSES_LIBS) - CURSES_INCLUDEDIR="$4" - search_ncurses=false - screen_manager=$5 - AC_DEFINE(HAS_CURSES) - has_curses=true - has_ncurses=true - AC_DEFINE(USE_NCURSES) - fi - fi -]) - -AC_DEFUN(AC_SEARCH_NCURSES, [ - AC_CHECKING("location of ncurses.h file") - - AC_NCURSES(/usr/include, ncurses.h, -lncurses,, "ncurses on /usr/include") - AC_NCURSES(/usr/include/ncurses, ncurses.h, -lncurses, -I/usr/include/ncurses, "ncurses on /usr/include/ncurses") - AC_NCURSES(/usr/local/include, ncurses.h, -L/usr/local/lib -lncurses, -I/usr/local/include, "ncurses on /usr/local") - AC_NCURSES(/usr/pkg/include, ncurses.h, -L/usr/pkg/lib -lncurses, -I/usr/pkg/include, "ncurses on /usr/pkg") - AC_NCURSES(/usr/contrib/include, ncurses.h, -L/usr/contrib/lib -lncurses, -I/usr/contrib/include, "ncurses on /usr/contrib") - AC_NCURSES(/usr/local/include/ncurses, ncurses.h, -L/usr/local/lib -L/usr/local/lib/ncurses -lncurses, -I/usr/local/include/ncurses, "ncurses on /usr/local/include/ncurses") - - AC_NCURSES(/usr/local/include/ncurses, curses.h, -L/usr/local/lib -lncurses, -I/usr/local/include/ncurses -DRENAMED_NCURSES, "renamed ncurses on /usr/local/.../ncurses") - - AC_NCURSES(/usr/include/ncurses, curses.h, -lncurses, -I/usr/include/ncurses -DRENAMED_NCURSES, "renamed ncurses on /usr/include/ncurses") - - dnl - dnl We couldn't find ncurses, try SysV curses - dnl - if $search_ncurses - then - AC_EGREP_HEADER(init_color, /usr/include/curses.h, - AC_USE_SYSV_CURSES) - AC_EGREP_CPP(USE_NCURSES,[ -#include -#ifdef __NCURSES_H -#undef USE_NCURSES -USE_NCURSES -#endif -],[ - CURSES_INCLUDEDIR="$CURSES_INCLUDEDIR -DRENAMED_NCURSES" - AC_DEFINE(HAS_CURSES) - has_curses=true - has_ncurses=true - AC_DEFINE(USE_NCURSES) - search_ncurses=false - screen_manager="ncurses installed as curses" -]) - fi - - dnl - dnl Try SunOS 4.x /usr/5{lib,include} ncurses - dnl The flags USE_SUNOS_CURSES, USE_BSD_CURSES and BUGGY_CURSES - dnl should be replaced by a more fine grained selection routine - dnl - if $search_ncurses - then - if test -f /usr/5include/curses.h - then - AC_USE_SUNOS_CURSES - fi - fi - - dnl use whatever curses there happens to be - if $search_ncurses - then - if test -f /usr/include/curses.h - then - CURSES_LIBS="-lcurses" - AC_DEFINE(HAS_CURSES) - has_curses=true - search_ncurses=false - screen_manager="curses" - fi - fi -]) - diff --git a/apps/irssi/default-config.h b/apps/irssi/default-config.h deleted file mode 100644 index 635030b8..00000000 --- a/apps/irssi/default-config.h +++ /dev/null @@ -1,67 +0,0 @@ -const char *default_config = -"lservers = (\n" -" { address = \"irc.stealth.net\"; chatnet = IRCNet; port = 6668; },\n" -" { address = \"irc.efnet.net\"; chatnet = EFNet; port = 6667; },\n" -" { address = \"irc.undernet.org\"; chatnet = Undernet; port = 6667; },\n" -" { address = \"irc.dal.net\"; chatnet = DALnet; port = 6667; },\n" -" { address = \"irc.openprojects.net\"; chatnet = OPN; port = 6667; },\n" -" { address = \"irc.ptlink.net\"; chatnet = PTlink; port = 6667; }\n" -" { address = \"silc.pspt.fi\"; chatnet = SILC; port = 706; }\n" -");\n" -"\n" -"chatnets = {\n" -" IRCNet = { type = \"IRC\"; max_kicks = 4; max_modes = 3; max_msgs = 5; max_whois = 4; };\n" -" EFNet = { type = \"IRC\"; max_kicks = 4; max_modes = 4; max_msgs = 3; };\n" -" Undernet = { type = \"IRC\"; max_kicks = 4; max_modes = 3; max_msgs = 3; max_query_chans = \"1\"; };\n" -" DALNet = { type = \"IRC\"; max_kicks = 4; max_modes = 6; max_msgs = 3; };\n" -" OPN = { type = \"IRC\"; max_kicks = 1; max_modes = 6; max_msgs = 100; };\n" -" PTLink = { type = \"IRC\"; max_kicks = 1; max_modes = 6; max_msgs = 100; };\n" -" SILC = { type = \"SILC\"; };\n" -"};\n" -"\n" -"channels = (\n" -" { name = \"#irssi\"; chatnet = ircnet; autojoin = No; },\n" -" { name = \"#irssi\"; chatnet = opn; autojoin = No; },\n" -" { name = \"#silc\"; chatnet = silc; autojoin = No; }\n" -");\n" -"\n" -"aliases = {\n" -" J = \"join\";\n" -" WJOIN = \"join -window\";\n" -" WQUERY = \"query -window\";\n" -" LEAVE = \"part\";\n" -" BYE = \"quit\";\n" -" EXIT = \"quit\";\n" -" SIGNOFF = \"quit\";\n" -" DESCRIBE = \"action\";\n" -" DATE = \"time\";\n" -" HOST = \"userhost\";\n" -" LAST = \"lastlog\";\n" -" SAY = \"msg *\";\n" -" WI = \"whois\";\n" -" WII = \"whois $0 $0\";\n" -" WW = \"whowas\";\n" -" W = \"who\";\n" -" N = \"names\";\n" -" M = \"msg\";\n" -" T = \"topic\";\n" -" C = \"clear\";\n" -" CL = \"clear\";\n" -" K = \"kick\";\n" -" KB = \"kickban\";\n" -" KN = \"knockout\";\n" -" BANS = \"ban\";\n" -" B = \"ban\";\n" -" MUB = \"unban *\";\n" -" UB = \"unban\";\n" -" IG = \"ignore\";\n" -" UNIG = \"unignore\";\n" -" SB = \"scrollback\";\n" -" UMODE = \"mode $N\";\n" -" WC = \"window close\";\n" -" WN = \"window new hide\";\n" -" SV = \"say Irssi $J - http://irssi.org/\";\n" -" GOTO = \"sb goto\";\n" -" CHAT = \"dcc chat\";\n" -"};\n" -; diff --git a/apps/irssi/default.theme b/apps/irssi/default.theme index 501a1617..4e1ec830 100644 --- a/apps/irssi/default.theme +++ b/apps/irssi/default.theme @@ -10,7 +10,7 @@ # up in those formats, and it was really hard to change the colors since you # might have had to change them in tens of different places. So, then came # this templating system. - + # Now the /FORMATs don't have any colors in them, and they also have very # little other styling. Most of the stuff you need to change is in this # theme file. If you can't change something here, you can always go back @@ -47,15 +47,16 @@ ############################################################################# -# default foreground color (%N) - 0 is the "default terminal color" +# default foreground color (%N) - 0 is the "default terminal color" default_color = 0; + # default foreground color when "0" can't be used, # such as with bolds and reverses. white is default. default_real_color = 7; # these characters are automatically replaced with specified color # (dark grey by default) -replaces = { "[]<>=" = "%K$0-%n"; }; +replaces = {}; abstracts = { ## @@ -63,10 +64,10 @@ abstracts = { ## # text to insert at the beginning of each non-message line - line_start = "%B-%W!%B-%n "; + line_start = "*** "; # timestamp styling, nothing by default - timestamp = "$0-"; + timestamp = "[$0-]"; # any kind of text that needs hilighting, default is to bold hilight = "%_$0-%_"; @@ -75,50 +76,71 @@ abstracts = { error = "%R$0-%n"; # channel name is printed - channel = "%_$0-%_"; + channel = "%c$0-%n"; # nick is printed - nick = "%_$0-%_"; + nick = "%c$0-%n"; # nick host is printed - nickhost = "[$0-]"; + nickhost = "($0-)"; # server name is printed - server = "%_$0-%_"; + server = "$0-"; # some kind of comment is printed - comment = "[$0-]"; + comment = "($0-)"; # reason for something is printed (part, quit, kick, ..) reason = "{comment $0-}"; - # mode change is printed ([+o nick]) - mode = "{comment $0-}"; + # mode change is printed + mode = "[$0-]"; ## ## channel specific messages ## # highlighted nick/host is printed (joins) - channick_hilight = "%C$0-%n"; + channick_hilight = "%c$0-%n"; chanhost_hilight = "{nickhost %c$0-%n}"; # nick/host is printed (parts, quits, etc.) - channick = "%c$0-%n"; + channick = "$0-"; chanhost = "{nickhost $0-}"; # highlighted channel name is printed channelhilight = "%c$0-%n"; # ban/ban exception/invite list mask is printed - ban = "%c$0-%n"; + ban = "$0-"; + + ## + ## Action (/ME command) + ## + + # Generic action + action = "%Y* $0 $1-"; + + # Own sent action + ownaction = "%c* $0 $1-"; + + ## + ## Notice (/NOTICE command) + ## + + # Generic notice + notice = "%C- $0 $1-"; + + # Own sent notice + ownnotice = "%g- $0 $1-"; + ## ## messages ## # the basic styling of how to print message, $0 = nick mode, $1 = nick - msgnick = "<$0$1-> %|"; + msgnick = "%c%|<$0$1->%n "; # message from you is printed. "msgownnick" specifies the styling of the # nick ($0 part in msgnick) and "ownmsgnick" specifies the styling of the @@ -132,109 +154,78 @@ abstracts = { # Example2.2: But you still want to keep <> grey for other messages: # pubmsgnick = "%K{msgnick $0 $1-%K}%n"; # pubmsgmenick = "%K{msgnick $0 $1-%K}%n"; - # pubmsghinick = "%K{msgnick $1 $0$2-%n%K}%n"; + # pubmsghinick = "%K{msgnick $1 $0$2-%K}%n"; # ownprivmsgnick = "%K{msgnick $0-%K}%n"; # privmsgnick = "%K{msgnick %R$0-%K}%n"; # $0 = nick mode, $1 = nick - ownmsgnick = "{msgnick $0 $1-}"; - ownnick = "%W$0-%n"; + ownmsgnick = "{msgnick $0 $1-}%g"; + ownnick = "$0-"; # public message in channel, $0 = nick mode, $1 = nick pubmsgnick = "{msgnick $0 $1-}"; - pubnick = "%N$0-%n"; + pubnick = "$0-"; # public message in channel meant for me, $0 = nick mode, $1 = nick - pubmsgmenick = "{msgnick $0 $1-}"; - menick = "%Y$0-%n"; + pubmsgmenick = "%g<$0$1->%n %|"; + menick = "$0-"; # public highlighted message in channel # $0 = highlight color, $1 = nick mode, $2 = nick - pubmsghinick = "{msgnick $1 $0$2-%n}"; + pubmsghinick = "{msgnick $1 $2-}$0"; # channel name is printed with message - msgchannel = "%K:%c$0-%n"; + msgchannel = "%w|%c$0-"; # private message, $0 = nick, $1 = host - privmsg = "[%R$0%K(%r$1-%K)%n] "; + privmsg = "*%c$0%n* "; # private message from you, $0 = "msg", $1 = target nick - ownprivmsg = "[%r$0%K(%R$1-%K)%n] "; - - # own private message in query - ownprivmsgnick = "{msgnick $0-}"; - ownprivnick = "%W$0-%n"; + ownprivmsg = "->*%c$1-%n* %g"; # private message in query - privmsgnick = "{msgnick %R$0-%n}"; - - ## - ## Actions (/ME stuff) - ## - - # used internally by this theme - action_core = "%W * $0-%n"; - - # generic one that's used by most actions - action = "{action_core $0-} "; - - # own action, both private/public - ownaction = "{action $0-}"; + privmsgnick = "*%c$0%n* "; - # own action with target, both private/public - ownaction_target = "{action_core $0}%K:%c$1%n "; - - # private action sent by others - pvtaction = "%W (*) $0-%n "; - pvtaction_query = "{action $0-}"; - - # public action sent by others - pubaction = "{action $0-}"; + # own private message in query + ownprivmsgnick = "->*%c$0%n* %g$1-"; + ownprivnick = "$0-"; ## ## other IRC events ## - # notices - ownnotice = "[%r$0%K(%R$1-%K)]%n "; - notice = "%K-%M$0-%K-%n "; - pubnotice_channel = "%K:%m$0-"; - pvtnotice_host = "%K(%m$0-%K)"; - servernotice = "%g!$0-%n "; - # CTCPs - ownctcp = "[%r$0%K(%R$1-%K)] "; - ctcp = "%g$0-%n"; + ownctcp = "[$0$1-] "; + ctcp = "$0-"; # wallops - wallop = "%W$0-%n: "; - wallop_nick = "%n$0-"; - wallop_action = "%W * $0-%n "; + wallop = "$0-: "; + wallop_nick = "$0-"; + wallop_action = " * $0- "; # netsplits - netsplit = "%R$0-%n"; + netsplit = "%c$0-%n"; netjoin = "%C$0-%n"; # /names list - names_nick = "[%_$0%_$1-] "; - names_users = "[%g$0-%n]"; - names_channel = "%G$0-%n"; + names_nick = "[ %n%_$0%_$1- ] "; + names_users = "$0-"; + names_channel = "{channel $0-}"; # DCC - dcc = "%g$0-%n"; + dcc = "$0-"; dccfile = "%_$0-%_"; # DCC chat, own msg/action - dccownmsg = "[%r$0%K($1-%K)%n] "; - dccownnick = "%R$0-%n"; + dccownmsg = "*%c=$1-%n*> %g"; dccownaction = "{action $0-}"; - dccownaction_target = "{action_core $0}%K:%c$1%n "; + dccownaction_target = "{ownaction_target $0-}"; # DCC chat, others - dccmsg = "[%G$1-%K(%g$0%K)%n] "; - dccquerynick = "%G$0-%n"; - dccaction = "%W (*dcc*) $0-%n %|"; + dccmsg = "*%c=$1-%n* "; + dccquerynick = "$0-"; + dccaction = " (*dcc*) $0- %|"; ## ## statusbar @@ -257,4 +248,15 @@ abstracts = { sbact = "{sb {sbact_act $0}{sbact_det $1}}"; sbact_act = "Act: $0-"; sbact_det = " Det: $0-"; + +}; + +# +# Some default formats how to print stuff on screen +# +formats = { + "fe-common/core" = { + endofnames = "{channel $0}: Total of {hilight $1} nicks {comment {hilight $2} ops, {hilight $4} normal}"; + line_start_irssi = "{line_start}"; + }; }; diff --git a/apps/irssi/docs/Makefile.am b/apps/irssi/docs/Makefile.am index 80499614..e78d297c 100644 --- a/apps/irssi/docs/Makefile.am +++ b/apps/irssi/docs/Makefile.am @@ -1,11 +1,14 @@ -docdir = $(prefix)/doc/irssi - -doc_DATA = \ - formats.txt \ - manual.txt \ - faq.txt \ - startup-HOWTO.html \ - startup-HOWTO.txt +include $(top_srcdir)/Makefile.defines.in + +#docdir = $(prefix)/doc/irssi +docdir = $(silc_docdir) + +doc_DATA = +# formats.txt \ +# manual.txt \ +# faq.txt \ +# startup-HOWTO.html \ +# startup-HOWTO.txt EXTRA_DIST = $(doc_DATA) diff --git a/apps/irssi/docs/help/Makefile.am.gen b/apps/irssi/docs/help/Makefile.am.gen index abf824b4..c836962b 100644 --- a/apps/irssi/docs/help/Makefile.am.gen +++ b/apps/irssi/docs/help/Makefile.am.gen @@ -1,6 +1,8 @@ # Makefile.am is autogenerated by autogen.sh from Makefile.am.gen -helpdir = $(datadir)/irssi/help +include $(top_srcdir)/Makefile.defines.in + +helpdir = $(silc_helpdir) help_DATA = \ @HELPFILES@ diff --git a/apps/irssi/docs/help/in/Makefile.am.gen b/apps/irssi/docs/help/in/Makefile.am.gen index 9b0f2aaa..7bf5760b 100644 --- a/apps/irssi/docs/help/in/Makefile.am.gen +++ b/apps/irssi/docs/help/in/Makefile.am.gen @@ -1,5 +1,4 @@ # Makefile.am is autogenerated by autogen.sh from Makefile.am.gen EXTRA_DIST = \ - Makefile.am.gen \ -@HELPFILES@ + Makefile.am.gen diff --git a/apps/irssi/docs/help/in/action.in b/apps/irssi/docs/help/in/action.in index 896e8d19..89123fcc 100644 --- a/apps/irssi/docs/help/in/action.in +++ b/apps/irssi/docs/help/in/action.in @@ -1,7 +1,7 @@ @SYNTAX:action@ -Same as ME, but gets channel or nick as an additional parameter. +Same as ME, but gets channel as an additional parameter. Example: /ACTION #irssi yawns (This outputs the following to #irssi: * Nick yawns) diff --git a/apps/irssi/docs/help/in/admin.in b/apps/irssi/docs/help/in/admin.in index b5d95254..f854cd0f 100644 --- a/apps/irssi/docs/help/in/admin.in +++ b/apps/irssi/docs/help/in/admin.in @@ -3,6 +3,9 @@ Displays the administrative details about the given server. If no server is specified, the server you are connected to is -used. If a nickname is supplied then it gives the administrative -information for that person's current server. +used. + +This command may be an alias. + +See also: INFO diff --git a/apps/irssi/docs/help/in/away.in b/apps/irssi/docs/help/in/away.in index 8283d63d..c83b275d 100644 --- a/apps/irssi/docs/help/in/away.in +++ b/apps/irssi/docs/help/in/away.in @@ -1,22 +1,17 @@ @SYNTAX:away@ - -one - -all - This command marks you as being "away". It is used to tell people that you currently aren't paying attention to your screen. You might use it if you are taking a nap, in the shower, getting some food, or otherwise -just aren't there at the moment. When you're "away" you will see "(zZzZ)" -in your statusbar. - -Anyone who does a WHOIS on your nickname will see that you are away, -as well as your away message. Anyone doing a WHO that returns information -about you will also see that you're gone. +just aren't there at the moment. By default, if someone sends you a MSG while you are away, your client will beep. You can turn this off by setting BEEP_WHEN_AWAY to OFF. +If someone sends you a message when you're away the set away message +will be automatically sent back to that person. + If you send a MSG to someone who is away, you will automatically be notified of this. By default, you will only receive this notification once. If you wish to see it every time (to tell when a person is no diff --git a/apps/irssi/docs/help/in/ban.in b/apps/irssi/docs/help/in/ban.in index 4f8a541d..32bf76e9 100644 --- a/apps/irssi/docs/help/in/ban.in +++ b/apps/irssi/docs/help/in/ban.in @@ -1,24 +1,28 @@ @SYNTAX:ban@ -Bans the specified nick or userhost mask. +This command is used to manage the ban list of the channel. +You must be channel operator to be able to use this command. +Wildcards may be used with this command. -If nick is given as parameter, the ban type is used to generate the ban -mask. /SET ban_type specified the default ban type. Ban type is one of -the following: +Examples: + /BAN #mychannel +foobar!mr.bar@foo.bar.com + Adds nickname `foobar' with username `mr.bar' from host + `foo.bar.com' on #mychannel to the ban list. - Normal - *!user@*.domain.net - Host - *!*@host.domain.net - Domain - *!*@*.domain.net - Custom [nick] [user] [host] [domain] + /BAN * +looser + Adds nickname `looser' to the ban list on current channel. -Examples: - /BAN looser - This bans the nick 'looser' - /BAN *!*@*.org - This bans all the users coming from any - .org domain. + /BAN * +foo*@*!@*.foobar.com + Adds foo* nicknames from any server with any username from + *.foobar.com hosts to the ban list on current channel. + + /BAN * -looser + Removes the nickname `looser' from the ban list on current + channel. - /SET ban_type custom nick domain - nick!*@*.domain.net - /SET ban_type custom user host - *!user@host.domain.net + /BAN * + Shows the ban list of the current channel. See also: KNOCKOUT, KICKBAN diff --git a/apps/irssi/docs/help/in/channel.in b/apps/irssi/docs/help/in/channel.in index 7484ac15..b11f3645 100644 --- a/apps/irssi/docs/help/in/channel.in +++ b/apps/irssi/docs/help/in/channel.in @@ -2,7 +2,7 @@ @SYNTAX:channel@ Irssi can automatically join to specified channels in specified -IRC networks. It can also automatically send the password when +networks. It can also automatically send the password when manually joining to channel without specifying the password. /CHANNEL ADD [-auto | -noauto] [-bots ] [-botcmd ] diff --git a/apps/irssi/docs/help/in/clear.in b/apps/irssi/docs/help/in/clear.in index b3db54ef..5c7e73be 100644 --- a/apps/irssi/docs/help/in/clear.in +++ b/apps/irssi/docs/help/in/clear.in @@ -4,6 +4,5 @@ This command clears the current window of all text. It is useful for wiping a screen that has rendered improperly (such as due to a bad termcap entry) or that contains sensitive information -(such as one's OPER password). diff --git a/apps/irssi/docs/help/in/close.in b/apps/irssi/docs/help/in/close.in new file mode 100644 index 00000000..7ce6dffd --- /dev/null +++ b/apps/irssi/docs/help/in/close.in @@ -0,0 +1,8 @@ + +@SYNTAX:close@ + +Operator command. Makes the server to close connection to another server +or router. + +See also: OPER, SILCOPER + diff --git a/apps/irssi/docs/help/in/cmode.in b/apps/irssi/docs/help/in/cmode.in new file mode 100644 index 00000000..495ec0ee --- /dev/null +++ b/apps/irssi/docs/help/in/cmode.in @@ -0,0 +1,35 @@ + +@SYNTAX:cmode@ + +This command is used to manage the modes of the channel. Most +of the modes require special privileges, such as channel operator +or channel founder privileges to work. The mode is added by +adding + before the option(s) and removed by adding - before the +option(s). The following modes are available: + + p Set/unset channel as private channel + s Set/unset channel as secret channel + k Set/unset that channel uses private channel key + i Set/unset channel as invite only channel + t Set/unset that only channel operator or + founder may set channel topic + l Set/unset channel's user limit + a Set/unset passphrase for channel that must + be provided when joining to the channel. + c Set/unset channel's cipher + h Set/unset channel's hmac + f <-pubkey| + Set/unset channel founder authentication. + Channel founder may set this mode so that + if the client leaves the channel it can + claim the founder rights when it returns + to the channel. If -pubkey is set then + the authentication will be done using the + client's public key. You can claim the + founder rights using the CUMODE command. + +Multiple modes can be set/unset at once if the modes does not +require any arguments. If mode requires an argument then only +one mode can be set at once + +See also: CUMODE, UMODE diff --git a/apps/irssi/docs/help/in/connect.in b/apps/irssi/docs/help/in/connect.in index f4671ab4..98c91680 100644 --- a/apps/irssi/docs/help/in/connect.in +++ b/apps/irssi/docs/help/in/connect.in @@ -2,7 +2,7 @@ @SYNTAX:connect@ -4, -6: specify explicitly whether to use IPv4 or IPv6 address - -ircnet: the IRCNet + -silcnet: the specified network -host: the host This command makes irssi to connect to specified server. diff --git a/apps/irssi/docs/help/in/ctcp.in b/apps/irssi/docs/help/in/ctcp.in deleted file mode 100644 index 77c78b7c..00000000 --- a/apps/irssi/docs/help/in/ctcp.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:ctcp@ - -Sends a CTCP-message. For example CTCP ACTION, or CTCP VERSION. - -See also: ME, ACTION - diff --git a/apps/irssi/docs/help/in/cumode.in b/apps/irssi/docs/help/in/cumode.in new file mode 100644 index 00000000..9bba22f3 --- /dev/null +++ b/apps/irssi/docs/help/in/cumode.in @@ -0,0 +1,34 @@ + +@SYNTAX:cumode@ + +This command is used to manage the client's modes on the channel. +Most of the modes require that the client which changes some +client's mode must be channel founder or channel operator. The +mode is added by adding + before the option(s) and removed by +adding - before the option(s). The following channel user modes +are available: + + a [@] + + Set/unset all modes (cannot be used to set + both founder and operator rights, can be used + only to remove both modes at once). + + f [@] [-pubkey|] + + Set/Unset channel founder. If the -pubkey + option or is provided then the + client is claiming the founder rights by + providing the channel founder authentication + data. If the -pubkey is provided then the + authentication is performed using the + client's public key. If you are channel + founder you can set the channel founder + authentication using CMODE command. + + o [@] + + Set/unset channel operator. Requires that + you are channel operator or channel founder. + +See also: CMODE, UMODE diff --git a/apps/irssi/docs/help/in/date.in b/apps/irssi/docs/help/in/date.in index 5b2ca4b9..85707719 100644 --- a/apps/irssi/docs/help/in/date.in +++ b/apps/irssi/docs/help/in/date.in @@ -9,3 +9,4 @@ If a nickname is given, that client's server is queried. Same as /TIME. +NOTE: This command has no effect on SILC. \ No newline at end of file diff --git a/apps/irssi/docs/help/in/dcc.in b/apps/irssi/docs/help/in/dcc.in deleted file mode 100644 index eb9caa34..00000000 --- a/apps/irssi/docs/help/in/dcc.in +++ /dev/null @@ -1,25 +0,0 @@ - -@SYNTAX:dcc@ - -This is a command to handle different DCC-connections. DCC is mainly -used for more reliable and faster chatting and for sending and receiving -files. - -/DCC LIST - - Shows all the open DCC connections. -/DCC RESUME [ []] - - Resumes a DCC SEND/GET connection. -/DCC CHAT [] - - Sends a chat connection request to remote client or accepts - a chat connection if the remote end has already sent a request. -/DCC GET [ []] - - Gets the file offered by remote client. The file is downloaded and - saved into the current working directory. -/DCC SEND - - Sends a DCC SEND request to remote client. Remote end has to accept - the request before the transmission can be started. -/DCC CLOSE [] - - Closes a DCC-connection. Type can be either SEND, GET or CHAT. - -See also: CD - diff --git a/apps/irssi/docs/help/in/deop.in b/apps/irssi/docs/help/in/deop.in deleted file mode 100644 index e9893e6a..00000000 --- a/apps/irssi/docs/help/in/deop.in +++ /dev/null @@ -1,10 +0,0 @@ - -@SYNTAX:deop@ - -Takes off the channel operator privileges from the -specified nick(s). - -Wildcards in the nick are allowed. - -See also: OP - diff --git a/apps/irssi/docs/help/in/devoice.in b/apps/irssi/docs/help/in/devoice.in deleted file mode 100644 index ce090191..00000000 --- a/apps/irssi/docs/help/in/devoice.in +++ /dev/null @@ -1,10 +0,0 @@ - -@SYNTAX:devoice@ - -Takes off the voice from the specified nick(s). This makes them -not to be able to send messages to the moderated (+m) channel. - -Wildcards in the nick are allowed. - -See also: VOICE, MODE - diff --git a/apps/irssi/docs/help/in/die.in b/apps/irssi/docs/help/in/die.in deleted file mode 100644 index 4ef6da0d..00000000 --- a/apps/irssi/docs/help/in/die.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:die@ - -IRC-operator command. Makes IRC-server to die. - -See also: OPER - diff --git a/apps/irssi/docs/help/in/disconnect.in b/apps/irssi/docs/help/in/disconnect.in index cfd221d5..b7f8c870 100644 --- a/apps/irssi/docs/help/in/disconnect.in +++ b/apps/irssi/docs/help/in/disconnect.in @@ -1,9 +1,7 @@ @SYNTAX:disconnect@ -Disconnects from the specified IRC-server. -The server tags can be seen with: -/SERVER LIST +Disconnects from the specified server. See also: CONNECT, SERVER diff --git a/apps/irssi/docs/help/in/file.in b/apps/irssi/docs/help/in/file.in new file mode 100644 index 00000000..4e85a92c --- /dev/null +++ b/apps/irssi/docs/help/in/file.in @@ -0,0 +1,43 @@ + +@SYNTAX:file@ + +This command is used to tranfer files between clients. +The actual file transfer stream is sent outside SILC network +peer to peer between the clients. Before the file transfer +begins the SILC Key Exchange protocol is performed between +the two clients to exchange key material. This key material +is then used to secure the file transfer stream between the +clients. + +The currently active file transfer sessions can be seen by +giving the FILE command without arguments. + +Commands: + + SEND [ []] + + Sends file transfer request to . This + makes the available to . + + If the is provided then the key exchange + protocol listener will be bound to that address. If + is defined it is bound to that port. + If they are not defined then the local IP address + of your machine is used to bind the listener. If that + fails then the is assumed to provide the + listener. If you do not know whether you need to + provide or not, do not provide it. + + RECEIVE [] + + Accepts the file transfer request and starts + the file transfer session. If the is + omitted the last received request is used. + + CLOSE [] + + Closes the file transfer session, or rejects + file transfer request. If this command is given + during the file transfer process it will be cancelled. + + diff --git a/apps/irssi/docs/help/in/getkey.in b/apps/irssi/docs/help/in/getkey.in new file mode 100644 index 00000000..38b63b49 --- /dev/null +++ b/apps/irssi/docs/help/in/getkey.in @@ -0,0 +1,9 @@ + +@SYNTAX:getkey@ + +This command is used to fetch remote client's or server's public +key. When fetching client's public key it is fetched from the +server the client is connected to. This way the public key might +have been verified already. However, you will be prompted to verify +the fetched public key. The public key is saved into your +local key directory (~/.silc/clientkeys/). diff --git a/apps/irssi/docs/help/in/hash.in b/apps/irssi/docs/help/in/hash.in deleted file mode 100644 index 62a889f8..00000000 --- a/apps/irssi/docs/help/in/hash.in +++ /dev/null @@ -1,5 +0,0 @@ - -@SYNTAX:hash@ - -Not available. - diff --git a/apps/irssi/docs/help/in/info.in b/apps/irssi/docs/help/in/info.in index e9aad17d..12e0b2f3 100644 --- a/apps/irssi/docs/help/in/info.in +++ b/apps/irssi/docs/help/in/info.in @@ -1,6 +1,7 @@ @SYNTAX:info@ -Shows information about the IRC creators, debuggers, slaves and -a lot of other people who no longer have much to do with irc. +Displays the administrative details about the given server. If +no server is specified, the server you are connected to is +used. diff --git a/apps/irssi/docs/help/in/invite.in b/apps/irssi/docs/help/in/invite.in index 1e7f69b1..b05fc42d 100644 --- a/apps/irssi/docs/help/in/invite.in +++ b/apps/irssi/docs/help/in/invite.in @@ -1,10 +1,24 @@ @SYNTAX:invite@ -Invites the specified nick to the current or specified channel. +This command is used to invite an client to a channel and to manage +the channel's invite list. Wildcards may be used with this command. -Example: - /INVITE buddy #mychannel +Examples: + /INVITE #silc joe + Invites nickname `joe' to channel #silc. -See also: MODE + /INVITE #silc +joe!*@* + Adds nickname `joe' from anywhere to the invite list of the + channel #silc + + /INVITE * +foo*@silcnet.org!*@*.foobar.com + Adds nicknames foo* from silcnet.org server from *.foobar.com + hosts to the invite list of the current channel. + + /INVITE * -joe + Removes nickname `joe' from the invite list of the current + channel. + +See also: CMODE diff --git a/apps/irssi/docs/help/in/invitelist.in b/apps/irssi/docs/help/in/invitelist.in deleted file mode 100644 index ed6bc060..00000000 --- a/apps/irssi/docs/help/in/invitelist.in +++ /dev/null @@ -1,9 +0,0 @@ - -@SYNTAX:invitelist@ - -Shows the +I modes of the current channel. +I mode -allows free joins of clients with certain userhost mask -even if the channel is invite only. - -See also: INVITE, MODE - diff --git a/apps/irssi/docs/help/in/ircnet.in b/apps/irssi/docs/help/in/ircnet.in deleted file mode 100644 index 51a123fe..00000000 --- a/apps/irssi/docs/help/in/ircnet.in +++ /dev/null @@ -1,21 +0,0 @@ - -@SYNTAX:ircnet@ - - -kicks: Maximum number of nicks in one /KICK command - -msgs: Maximum number of nicks in one /MSG command - -modes: Maximum number of mode changes in one /MODE command - -whois: Maximum number of nicks in one /WHOIS command - -cmdspeed: Same as /SET cmd_queue_speed, see section 3.1 - -cmdmax: Same as /SET cmd_max_at_once, see section 3.1 - -nick, -user, -realname: Specify what nick/user/name to use - -host: Specify what host name to use, if you have multiple - -autosendcmd: Command to send after connecting to a server - -With -autosendcmd argument you can automatically run any commands -after connecting to ircnet. This is useful for automatically -identifying yourself to NickServ, for example - -Shows and changes the settings of defined IRC networks. - -See also: CONNECT - diff --git a/apps/irssi/docs/help/in/ison.in b/apps/irssi/docs/help/in/ison.in deleted file mode 100644 index b97ff5de..00000000 --- a/apps/irssi/docs/help/in/ison.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:ison@ - -Tells whether specified nicks are online. - -See also: WHOIS, WHOWAS, NOTIFY - diff --git a/apps/irssi/docs/help/in/join.in b/apps/irssi/docs/help/in/join.in index bdd4b15c..424362af 100644 --- a/apps/irssi/docs/help/in/join.in +++ b/apps/irssi/docs/help/in/join.in @@ -2,10 +2,9 @@ @SYNTAX:join@ Joins a specified channel. Channel names usually begin with #-sign, -which may be omitted here. +but note that the #-sign is not mandatory in channel names. -JOIN is aliased to J by default. Example: /j irssi -(This joins to the channel #irssi) +JOIN is aliased to J by default. Description diff --git a/apps/irssi/docs/help/in/key.in b/apps/irssi/docs/help/in/key.in new file mode 100644 index 00000000..b4874c91 --- /dev/null +++ b/apps/irssi/docs/help/in/key.in @@ -0,0 +1,95 @@ + +@SYNTAX:key@ + +This command is used to set and unset private keys for +channels, set and unset private keys for private messages +with remote clients and to send key agreement requests and +negotiate the key agreement protocol with remote client. +The key agreement is supported only to negotiate private +message keys, it currently cannot be used to negotiate +private keys for channels, as it is not convenient for that +purpose. + +Types: + + MSG The command is performed for private messages + affecting the . + + CHANNEL The command is performed for channel affecting + the . + +Commands: + + set [ [] []] + + Set the key into use. If the is provided it + is used as the key material. If the is not + provided the negotiated key material is used. If + the negotiation has not been performed this command + has no effect. + + If the type is `msg' and the is `*' then + random key will be generated automatically. + + The may be set for both private message + and channel private keys and the may be set + only to the channel private keys. + + unset [] + + Unset the key. The private key is not used after + this command. The key must be set again or the key + material must be re-negotiated to be able to use + the private keys again. + + The channel may have several private keys set. The + can be used to indicate what key is being + unset. If it is not provided all keys are removed. + + list + + List all private keys that has been set. If the + type is `msg' and the is ´*' then + all private message keys that you've set will be + listed. + + agreement [ []] + + Send key agreement request to remote client. If + the is provided it is sent in the request. + The receiver may use the hostname to start the + key agreement. If the is also provided your + key agreement protocol server is bound to that + port. Note that it cannot be privileged port (<1024). + If the and is not provided then + the receiver will never initiate the key agreement. + In this case you may start the key agreement after + receiving the reply to the request, by giving the + negotiate command. + + This command may be used to send reply to the + remote client. When receiving empty key agreement + you can reply to the sender with the hostname and + port of your key agreement server with this command. + + If the hostname and port are ommitted, the irssi + boolean variable use_auto_addr will be examined. If + this variable is set, the value of auto_bind_ip will + be used as the IP address to listen for the return + reply, the value of auto_public_ip will be the IP + address sent to the remote client, and auto_bind_port + will be the port value to be bound to AND sent to + the remote client. If auto_public_ip is unset, but + auto_bind_ip IS, irssi will send the auto_bind_ip + variable's value to the remote client. + + negotiate [ []] + + This may be called to start the key agreement with + . This command has effect only if the + has replied to your key agreement request. + You will see a notify on the screen when the reply + arrives. The and is the hostname + and port of the remote client's key agreement + server. + diff --git a/apps/irssi/docs/help/in/kick.in b/apps/irssi/docs/help/in/kick.in index 304c4d31..3e73dbe2 100644 --- a/apps/irssi/docs/help/in/kick.in +++ b/apps/irssi/docs/help/in/kick.in @@ -1,14 +1,10 @@ @SYNTAX:kick@ -This command "kicks" the specified user off of the specified -channel. It is typically used to remove troublemakers, flooders, -or people otherwise making a nuisanse of themselves on the channel. -The reason for the kick is recommended, but not required by the IRC -servers - -If the is omitted, removes the nick from the current -channel. +This command kicks client from channel. You have to be +at least channel operator to be able to kick client from +channel. Note: you cannot kick channel founder even if +you are channel operator. The default alias for /KICK is /K. diff --git a/apps/irssi/docs/help/in/kill.in b/apps/irssi/docs/help/in/kill.in index 338871d9..8b3b91cd 100644 --- a/apps/irssi/docs/help/in/kill.in +++ b/apps/irssi/docs/help/in/kill.in @@ -1,18 +1,11 @@ @SYNTAX:kill@ -IRC operator command. +This is operator command. KILL is used to forcibly remove +a client from the network. It works similarly to KICK expect +that the client is removed from the entire network. In general, +KILL is useful only as a warning tool for abusive users and +it has only temporary effects. -KILL is used to forcibly remote a client from the irc network. -It works similarly to KICK, except that a reason must be -given (even if it is meaningless or flat-out wrong). - -In general, KILL is useful only as a warning tool for abusive -users. Modern irc clients (this one included) have automated -means for reconnecting to a server after a disconnection (whether -due to a KILL or something else), so KILL is by no means a -permanent solution. It is not intended as a means for personal -vendettas; this practice is generally frowned upon. - -See also: OPER +See also: OPER, SILCOPER diff --git a/apps/irssi/docs/help/in/layout.in b/apps/irssi/docs/help/in/layout.in index d5e81438..d0cde15e 100644 --- a/apps/irssi/docs/help/in/layout.in +++ b/apps/irssi/docs/help/in/layout.in @@ -2,9 +2,9 @@ @SYNTAX:layout@ Saves the current window layout to configuration (yes, you'll still -need to use /SAVE to save the configuration to file). Next time you run -irssi, all the channels and queries are exactly in the same windows -where they were when you called /LAYOUT SAVE. +need to use /SAVE to save the configuration to file). Next time you +run irssi, all the channels and queries are exactly in the same +windows where they were when you called /LAYOUT SAVE. Channels aren't actually joined in those windows immediately, they're just marked "next time you join to '#channel' in server that has tag diff --git a/apps/irssi/docs/help/in/links.in b/apps/irssi/docs/help/in/links.in deleted file mode 100644 index 726fcb6b..00000000 --- a/apps/irssi/docs/help/in/links.in +++ /dev/null @@ -1,9 +0,0 @@ - -@SYNTAX:links@ - -Shows the links between the IRC servers of the -current IRC network. If a wildcard parameter is -specified, shows only the matching entries. - -See also: - diff --git a/apps/irssi/docs/help/in/load.in b/apps/irssi/docs/help/in/load.in index 134c7623..16ce9be5 100644 --- a/apps/irssi/docs/help/in/load.in +++ b/apps/irssi/docs/help/in/load.in @@ -1,8 +1,8 @@ @SYNTAX:load@ -Load a plugin. If full path isn't given, irssi searches the plugin from -directories: +Load a plugin. If full path isn't given, irssi searches the +plugin from directories: ~/.irssi/modules/ /lib/irssi/modules/ diff --git a/apps/irssi/docs/help/in/lusers.in b/apps/irssi/docs/help/in/lusers.in deleted file mode 100644 index abcd55c4..00000000 --- a/apps/irssi/docs/help/in/lusers.in +++ /dev/null @@ -1,5 +0,0 @@ - -@SYNTAX:lusers@ - -Shows user statistics of the current IRC network. - diff --git a/apps/irssi/docs/help/in/map.in b/apps/irssi/docs/help/in/map.in deleted file mode 100644 index 824dce37..00000000 --- a/apps/irssi/docs/help/in/map.in +++ /dev/null @@ -1,5 +0,0 @@ - -@SYNTAX:map@ - -Not available in IRC. - diff --git a/apps/irssi/docs/help/in/me.in b/apps/irssi/docs/help/in/me.in index b918ec71..44871274 100644 --- a/apps/irssi/docs/help/in/me.in +++ b/apps/irssi/docs/help/in/me.in @@ -1,8 +1,7 @@ @SYNTAX:me@ -Sends a CTCP ACTION to the current channel or query. -For example: /me sits back. - -See also: ACTION, CTCP +Sends an ACTION channel message to the current channel. +For example: /ME sits back. +See also: ACTION diff --git a/apps/irssi/docs/help/in/mircdcc.in b/apps/irssi/docs/help/in/mircdcc.in deleted file mode 100644 index d3402e0a..00000000 --- a/apps/irssi/docs/help/in/mircdcc.in +++ /dev/null @@ -1,11 +0,0 @@ - -@SYNTAX:mircdcc@ - -Selects whether to send mIRC style CTCPs in DCC chat -session. - -If a mIRC user sends first a CTCP, mIRC style CTCPs is -automatically selected for that DCC Chat session. - -See also: SET MIRC - diff --git a/apps/irssi/docs/help/in/mode.in b/apps/irssi/docs/help/in/mode.in deleted file mode 100644 index 5bef27c4..00000000 --- a/apps/irssi/docs/help/in/mode.in +++ /dev/null @@ -1,97 +0,0 @@ - -@SYNTAX:mode@ - -Irssi knows these channel modes: - - i - Invite only - People can't join to channel without being - /INVITEd, or being in invite list (+I, see below). - m - Moderated - People who don't have voices (+v) can't send - messages to channel - p - Private - People who aren't joined to channel can't see it - for example with /WHOISing people who are in channel. - s - Secret - Like private, but the channel isn't displayed in - /LIST's output. - n - No external msgs - Without this mode, anyone can send messages - to channel without even being joined. - t - Topic can be changed only by channel operators. - - k - Channel password (aka. key) - The channel can't be joined - without specifying the channel key (see section 6.2). - - l - User limit - No more than people can join to - channel. This can be overridden with /INVITE with some - servers. - - This is usually used for protecting channel from join - flooding, like some bot allows max. 5 users to join in - one minute or so. - - a - Anonymous - No-one's nick name, host or anything else can be - seen. All messages, joins, parts, modes, etc. are seen as coming - from nick "anonymous", this could be pretty confusing but nice - feature if you want total anonymity. This mode can only be set, - never unset. This mode isn't supported by all servers. - - NOTE: there is/was one bug :) Channel operators can guess if some - nick might be in the channel and try to kick it. If nick was in - channel, everyone will see the nick that was kicked. - - r - Re-op - If channel becomes opless for longer than 45 (?) minutes, - op everyone in the channel. This works only in !channels. This - mode can only be set, not unset by channel creator. - - b - Set/remove ban. For example MODE #channel +b *!*@*.org bans - everyone from .org domain. - - If someone from .org domain was already in channel before the - ban was set, he/she couldn't be able to write any messages to - channel (doesn't work with all servers). - - Ban can also be overridden with /INVITE, although many stupid - IRC clients automatically kick the user out because they see - the ban and think that because of it the user shouldn't be in - the channel (doesn't work with all servers). - - e - Ban exceptions. You could for example ban everyone from - *!*@*.org but set ban exception to *!*@*.host.org - works only - in IRCnet/EFnet servers. - - I - Invite list. If channel is invite only (+i), people in this - list can join it without being /INVITEd - works only in - IRCnet/EFnet servers. - - This is excellent for in-country channels that don't want - foreigners (spammers!) to join the channel, for example setting - channel's mode to +i and +I *!*@*.fi allows only finnish people - to join the channel. In addition to this, there's usually a bot - in the channels and sending /MSG bot invite command to it - /INVITEs you to the channel. - - The ':' feature in channel modes is quite similiar, see section - 6.2. - - O - Channel owner, the nick who creates a !channel receives this - mode. It isn't displayed anywhere, you can't pass it to anyone - else and you can't regain it again. This is needed for setting - +r mode in channel when it's first created. - - o - Grant or revoke channel operator status from nick - v - Grant or revoke voice status from nick, only people with - +v (or +o) can talk to channel when it's moderated (+m). - -You can send multiple mode changes with one mode command: - -/MODE #channel +nto-o+v nick1 nick2 nick3 - -This would set channel's mode to +nt, give ops to nick1, take ops -from nick2 and give voices to nick3. - -You can set only limited number of modes that requires argument in -one command. In IRCnet it's 3, in EFnet it's 4 and in many others -it's 6. If it's not known, Irssi defaults to 3. Irssi will also -automatically split them, so you can use /MODE +oooooo n1,n2,.. -command to op 6 people and Irssi will split it to two commands in -IRCnet/EFnet. - -See also: OP, DEOP, VOICE, DEVOICE, BAN, UNBAN - diff --git a/apps/irssi/docs/help/in/msg.in b/apps/irssi/docs/help/in/msg.in index f58ba41a..5224b27a 100644 --- a/apps/irssi/docs/help/in/msg.in +++ b/apps/irssi/docs/help/in/msg.in @@ -11,5 +11,3 @@ Examples: /MSG #irssi Hello, is the new gtk-version out already? (This format is rarely needed.) -See also: CTCP - diff --git a/apps/irssi/docs/help/in/names.in b/apps/irssi/docs/help/in/names.in index 2d515c1c..d3512d0b 100644 --- a/apps/irssi/docs/help/in/names.in +++ b/apps/irssi/docs/help/in/names.in @@ -6,8 +6,9 @@ -voices: show voiced people in list -normal: show rest of the people in list -Shows the names (nicks) in the specified channels. /NAMES ** shows all -nicks in all channels, you probably don't want to do this. +Shows the names (nicks) in the specified channels. /NAMES ** +shows all nicks in all channels, you probably don't want +to do this. Examples: diff --git a/apps/irssi/docs/help/in/nctcp.in b/apps/irssi/docs/help/in/nctcp.in deleted file mode 100644 index 58b80545..00000000 --- a/apps/irssi/docs/help/in/nctcp.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:nctcp@ - -Sends a CTCP reply notice to the nick/channel. - -See also: CTCP, ACTION, MSG, NOTICE - diff --git a/apps/irssi/docs/help/in/netsplit.in b/apps/irssi/docs/help/in/netsplit.in deleted file mode 100644 index 2e244ca1..00000000 --- a/apps/irssi/docs/help/in/netsplit.in +++ /dev/null @@ -1,6 +0,0 @@ - -@SYNTAX:netsplit@ - -Irssi keeps track of people who were lost in net splits. With this -command you can get a list of them. - diff --git a/apps/irssi/docs/help/in/nick.in b/apps/irssi/docs/help/in/nick.in index 57313b7f..d657288d 100644 --- a/apps/irssi/docs/help/in/nick.in +++ b/apps/irssi/docs/help/in/nick.in @@ -1,6 +1,6 @@ @SYNTAX:nick@ -Changes your nick. This should be hardly rarely -used or needed. +Changes your nickname. + diff --git a/apps/irssi/docs/help/in/note.in b/apps/irssi/docs/help/in/note.in deleted file mode 100644 index d77bb230..00000000 --- a/apps/irssi/docs/help/in/note.in +++ /dev/null @@ -1,28 +0,0 @@ - -@SYNTAX:note@ - -NOTE is a sort of turbo-charged messaging system for irc. In short, -it achieves at the server level what the client attempts to do with MSG -and NOTIFY. The messaging system resembles modern voicemail systems -(except in text); messages can be sent, stored, or set for deferred -delivery. The client notification system works like NOTIFY, except with -greater accuracy and flexibility. - -The most common uses of NOTE are its SPY and SEND functions. SPY is similar -to NOTIFY, except it can accept a full address to spy on, not just a nickname. -SEND, as its name implies, sends a note to a user; if that user is not currently -online, it will be delivered if the user logs onto irc within a set time period. - -When referring to a particular user, NOTE can deal with the standard -nick!user@host notation. Wildcards are allowed, and any portion may be omitted, -so long as the identifier remains unambiguous. - -Examples: -To send a note to Joebob (whose account is jbriggs@drivein.com): -/NOTE SEND joebob!jbriggs@drivein.com Hey there! Great movie! - -To spy on anyone from blah.com for the next 30 days: -/NOTE SPY +30 *!*@*.blah.com A blah.com user is active - -This command is Not available in the IRCNet. - diff --git a/apps/irssi/docs/help/in/notice.in b/apps/irssi/docs/help/in/notice.in index 6b4da9b0..e93d2c2a 100644 --- a/apps/irssi/docs/help/in/notice.in +++ b/apps/irssi/docs/help/in/notice.in @@ -1,10 +1,8 @@ @SYNTAX:notice@ -Sends a notice to the nick or the channel. Usually notices are -used in bots and scripts for different kinds of replies. The -IRC protocol states that notices may not generate replies to -avoid msg loops. +Sends a notice to the nick or the channel. Usually notices +are used in bots and scripts for different kinds of replies. -See also: NCTCP, MSG +See also: ACTION diff --git a/apps/irssi/docs/help/in/notify.in b/apps/irssi/docs/help/in/notify.in deleted file mode 100644 index 5f278e0c..00000000 --- a/apps/irssi/docs/help/in/notify.in +++ /dev/null @@ -1,15 +0,0 @@ - -@SYNTAX:notify@ - - -away: Notifies about away-status changes - -idle: Notifies if idle time is first larger than - (default is hour) and then it drops down. - -list: Lists the notify list entries with all their settings - : Either a simple "nick" or "nick!*@*blah.org". - The nick can't contain wildcards, but the user/host can. - -/NOTIFY without any arguments displays if the people in notify -list are online or offline. - -See also: UNNOTIFY, SET NOTIFY - diff --git a/apps/irssi/docs/help/in/op.in b/apps/irssi/docs/help/in/op.in deleted file mode 100644 index ce46cb63..00000000 --- a/apps/irssi/docs/help/in/op.in +++ /dev/null @@ -1,8 +0,0 @@ - -@SYNTAX:op@ - -Gives the channel operator privileges for the specified -nick(s). Wildcards in the nick are allowed. - -See also: DEOP, MODE, VOICE, DEVOICE, KICK - diff --git a/apps/irssi/docs/help/in/oper.in b/apps/irssi/docs/help/in/oper.in index 6cb6518c..40dc9c54 100644 --- a/apps/irssi/docs/help/in/oper.in +++ b/apps/irssi/docs/help/in/oper.in @@ -1,10 +1,9 @@ @SYNTAX:oper@ -Gives you operator priviledges if the correct nickname and -password are given. If password is not given, you will be -prompted for one. If no nickname is given, your current -nickname will be used. +Gives you server operator priviledges if the correct +username and passphrase are given. User will be prompted +for the passphrase if the -pubkey option is not provided. -See also: KILL, DIE +See also: KILL, SCONNECT, CLOSE, SILCOPER diff --git a/apps/irssi/docs/help/in/perlflush.in b/apps/irssi/docs/help/in/perlflush.in deleted file mode 100644 index 27fc509f..00000000 --- a/apps/irssi/docs/help/in/perlflush.in +++ /dev/null @@ -1,8 +0,0 @@ - -@SYNTAX:perlflush@ - -Stops and removes all Perl scripts which have been run. -Also undefines all the commands defined by Perl scripts. - -See also: RUN - diff --git a/apps/irssi/docs/help/in/ping.in b/apps/irssi/docs/help/in/ping.in index 3518b0c9..806e1b9b 100644 --- a/apps/irssi/docs/help/in/ping.in +++ b/apps/irssi/docs/help/in/ping.in @@ -1,10 +1,7 @@ @SYNTAX:ping@ -Sends CTCP PING to another IRC client. This is used -to find out the speed of IRC network. When the PONG -reply comes in, irssi shows the interval time between -sending the request and receiving the reply. +Sends PING to the connected server. + -See also: CTCP diff --git a/apps/irssi/docs/help/in/query.in b/apps/irssi/docs/help/in/query.in index 818bbe64..52f191ee 100644 --- a/apps/irssi/docs/help/in/query.in +++ b/apps/irssi/docs/help/in/query.in @@ -7,5 +7,7 @@ the specified nick in the form of MSGs. Usually this command opens up a new window, too. -See also: WINDOW, MSG, SET QUERY +The query is ended by giving command UNQUERY + +See also: UNQUERY, WINDOW, MSG, SET QUERY diff --git a/apps/irssi/docs/help/in/quit.in b/apps/irssi/docs/help/in/quit.in index f7536211..1c1e7bac 100644 --- a/apps/irssi/docs/help/in/quit.in +++ b/apps/irssi/docs/help/in/quit.in @@ -1,7 +1,7 @@ @SYNTAX:quit@ -This ends your irc session. If a quit message is supplied, it +This ends your session. If a quit message is supplied, it will be displayed to anyone else on any channel you were on before quitting. If one isn't specified, the text "Leaving" is used. diff --git a/apps/irssi/docs/help/in/quote.in b/apps/irssi/docs/help/in/quote.in deleted file mode 100644 index eceb409c..00000000 --- a/apps/irssi/docs/help/in/quote.in +++ /dev/null @@ -1,8 +0,0 @@ - -@SYNTAX:quote@ - -Sends server raw data without parsing. - -Example: - /QUOTE PRIVMSG cras :Hey, this works! - diff --git a/apps/irssi/docs/help/in/rawlog.in b/apps/irssi/docs/help/in/rawlog.in deleted file mode 100644 index b863e234..00000000 --- a/apps/irssi/docs/help/in/rawlog.in +++ /dev/null @@ -1,15 +0,0 @@ - -@SYNTAX:rawlog@ - -All data that is received or sent to server is kept in a raw log -buffer for a while. Also event redirections are kept there. This is -very useful for debugging purposes. - -/RAWLOG SAVE - Save the current raw log buffer to file -/RAWLOG OPEN - Like /RAWLOG SAVE, but keep the log file - open and write all new log to it. -/RAWLOG CLOSE - Close the open raw log - -/SET rawlog_lines - Specify the number of raw log lines to - keep in memory. - diff --git a/apps/irssi/docs/help/in/rehash.in b/apps/irssi/docs/help/in/rehash.in deleted file mode 100644 index 2fb55791..00000000 --- a/apps/irssi/docs/help/in/rehash.in +++ /dev/null @@ -1,11 +0,0 @@ - -@SYNTAX:rehash@ - -IRC Operator command. - -This command is used to force the current server to reload it's -ircd.conf configuration file. This is useful for effecting -configuration changes without starting a new server. - -See also: OPER, RESTART - diff --git a/apps/irssi/docs/help/in/restart.in b/apps/irssi/docs/help/in/restart.in deleted file mode 100644 index 14cd940f..00000000 --- a/apps/irssi/docs/help/in/restart.in +++ /dev/null @@ -1,12 +0,0 @@ - -@SYNTAX:restart@ - -IRC Operator command. - -This command is used to completely restart the server. A side effect of -this is that the configuration file will be read again. However, it is -generally more useful for clearing out internal buffers and other -wasted memory. - -See also: OPER, DIE - diff --git a/apps/irssi/docs/help/in/rmrejoins.in b/apps/irssi/docs/help/in/rmrejoins.in index a485e93a..35e8ea3b 100644 --- a/apps/irssi/docs/help/in/rmrejoins.in +++ b/apps/irssi/docs/help/in/rmrejoins.in @@ -1,9 +1,9 @@ @SYNTAX:rmrejoins@ -Removes the pending rejoins from the channel rejoin list in active -server. Channels are added to rejoin list when join failed because of -netsplits in server ("Channel is temporarily unavailable"). +Removes the pending rejoins from the channel rejoin list in +active server. Channels are added to rejoin list when join +failed because of netsplits in server. See also: JOIN diff --git a/apps/irssi/docs/help/in/rping.in b/apps/irssi/docs/help/in/rping.in deleted file mode 100644 index e0623173..00000000 --- a/apps/irssi/docs/help/in/rping.in +++ /dev/null @@ -1,12 +0,0 @@ - -@SYNTAX:rping@ - -IRC Operator command. - -This command works like the PING command (CTCP PING), except -it is used on a server instead of a client. As with PING, it -is used to test the relative distance another server is from -you across the irc network. - -See also: OPER - diff --git a/apps/irssi/docs/help/in/run.in b/apps/irssi/docs/help/in/run.in deleted file mode 100644 index 982808cb..00000000 --- a/apps/irssi/docs/help/in/run.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:run@ - -Runs a perl-script. For more information, see the -perl.txt in the docs-directory of irssi. - -See also: PERLFLUSH diff --git a/apps/irssi/docs/help/in/sconnect.in b/apps/irssi/docs/help/in/sconnect.in index 0f19d349..0d2137ba 100644 --- a/apps/irssi/docs/help/in/sconnect.in +++ b/apps/irssi/docs/help/in/sconnect.in @@ -1,8 +1,8 @@ @SYNTAX:sconnect@ -IRC Operator command. Makes an IRC server to connect -to another server. +Operator command. Makes an server to connect to another +server or router. -See also: OPER, SQUIT, RESTART +See also: OPER, SILCOPER, CLOSE, SHUTDOWN diff --git a/apps/irssi/docs/help/in/server.in b/apps/irssi/docs/help/in/server.in index f75462cb..75170f12 100644 --- a/apps/irssi/docs/help/in/server.in +++ b/apps/irssi/docs/help/in/server.in @@ -4,7 +4,7 @@ -4, -6: specify explicitly whether to use IPv4 or IPv6 address -auto: Automatically connect to server at startup (default) -noauto: Don't connect to server at startup - -ircnet: Specify what IRC network this server belongs to + -silcnet: Specify what network this server belongs to -host: Specify what host name to use, if you have multiple -cmdspeed: Same as /SET cmd_queue_speed, see section 3.1 -cmdmax: Same as /SET cmd_max_at_once, see section 3.1 diff --git a/apps/irssi/docs/help/in/servlist.in b/apps/irssi/docs/help/in/servlist.in deleted file mode 100644 index cee9612d..00000000 --- a/apps/irssi/docs/help/in/servlist.in +++ /dev/null @@ -1,19 +0,0 @@ - -@SYNTAX:servlist@ - -SERVLIST gives the list of services currently present on the -IRC network. It can take two arguments. - limits the output to the services which names matches - the mask. - limits the output to the services of the specified type. - -The fields returned are: - Service name. - Server who introduced the service. - Distribution mask. - Service type. - Hop count to the service. - A comment. - -See also: SQUERY - diff --git a/apps/irssi/docs/help/in/shutdown.in b/apps/irssi/docs/help/in/shutdown.in new file mode 100644 index 00000000..48ff58c9 --- /dev/null +++ b/apps/irssi/docs/help/in/shutdown.in @@ -0,0 +1,7 @@ + +@SYNTAX:shutdown@ + +Operator command. Shutdowns the server. + +See also: OPER, SILCOPER, CLOSE + diff --git a/apps/irssi/docs/help/in/silcoper.in b/apps/irssi/docs/help/in/silcoper.in new file mode 100644 index 00000000..5247bf46 --- /dev/null +++ b/apps/irssi/docs/help/in/silcoper.in @@ -0,0 +1,12 @@ + +@SYNTAX:silcoper@ + +Gives you router operator priviledges if the correct +username and passphrase are given. User will be prompted +for the passphrase if the -pubkey option is not provided. + +NOTE: This command works only on router server. It has +no effect on normal SILC server. + +See also: KILL, SCONNECT, CLOSE, OPER + diff --git a/apps/irssi/docs/help/in/silence.in b/apps/irssi/docs/help/in/silence.in deleted file mode 100644 index 4e37d7b7..00000000 --- a/apps/irssi/docs/help/in/silence.in +++ /dev/null @@ -1,22 +0,0 @@ - -@SYNTAX:silence@ - -Works only in the Undernet and Open Projects (ircu). - -SILENCE is similar in many respects to IGNORE, except that it is -server-based. What this means is the server will never even send -you messages from anyone you have SILENCEd, whereas it will with -IGNORE, where your client is responsible for filtering the messages -out. This has the advantage of not bogging your client down with -excessive data as it tries to filter out messages. - -The default behavior is to SILENCE a nick!user@host pattern, and -if such a pattern is not passed as the argument, it must be prepended -with a plus ('+') to be added to your silence list. If a pattern is -prepended with a minus ('-'), it will be removed from your silence list. -If you only specify a nickname, you can list the patterns in the -silence list owned by that nickname. If no arguments are given, your -own silence list is displayed. - -See also: IGNORE - diff --git a/apps/irssi/docs/help/in/squery.in b/apps/irssi/docs/help/in/squery.in deleted file mode 100644 index a0bb4f11..00000000 --- a/apps/irssi/docs/help/in/squery.in +++ /dev/null @@ -1,10 +0,0 @@ - -@SYNTAX:squery@ - - - Service name - - Commands to pass to the service. - -/SQUERY sends a query to the specified service. - -See also: SERVLIST - diff --git a/apps/irssi/docs/help/in/squit.in b/apps/irssi/docs/help/in/squit.in deleted file mode 100644 index 64b8616c..00000000 --- a/apps/irssi/docs/help/in/squit.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:squit@ - -IRC Operator command. Makes server to quit IRC network. - -See also: OPER, DIE, RESTART - diff --git a/apps/irssi/docs/help/in/stats.in b/apps/irssi/docs/help/in/stats.in deleted file mode 100644 index 3afc6ebd..00000000 --- a/apps/irssi/docs/help/in/stats.in +++ /dev/null @@ -1,20 +0,0 @@ - -@SYNTAX:stats@ - -Shows some irc server usage statistics. - c - Shows C and N lines for a given server. These are - the names of the servers that are allowed to connect. - h - Shows H and L lines for a given server (Hubs and Leaves). - k - Show K lines for a server. This shows who is not - allowed to connect and possibly at what time they are - not allowed to connect. - i - Shows I lines. This is who CAN connect to a server. - l - Shows information about amount of information passed - to servers and users. - m - Shows a count for the number of times the various - commands have been used since the server was booted. - o - Shows the list of authorized operators on the server. - u - Shows the uptime for a server - y - Shows Y lines, which lists the various connection - classes for a given server. - diff --git a/apps/irssi/docs/help/in/time.in b/apps/irssi/docs/help/in/time.in deleted file mode 100644 index 7877fe75..00000000 --- a/apps/irssi/docs/help/in/time.in +++ /dev/null @@ -1,11 +0,0 @@ - -@SYNTAX:time@ - -This displays the time of day, local to the server queried (thus, -the time returned may not be the same as the client's local time). - -If the server name is omitted, the client's current server is used. -If a nickname is given, that client's server is queried. - -Same as /DATE. - diff --git a/apps/irssi/docs/help/in/topic.in b/apps/irssi/docs/help/in/topic.in index ae2564f8..bf44a3ad 100644 --- a/apps/irssi/docs/help/in/topic.in +++ b/apps/irssi/docs/help/in/topic.in @@ -1,8 +1,6 @@ @SYNTAX:topic@ - -delete - Deletes the topic. - -Shows or/and changes the topic of the current or specified -channel. +Shows or/and changes the topic of the current or +specified channel. diff --git a/apps/irssi/docs/help/in/trace.in b/apps/irssi/docs/help/in/trace.in deleted file mode 100644 index 27df384c..00000000 --- a/apps/irssi/docs/help/in/trace.in +++ /dev/null @@ -1,8 +0,0 @@ - -@SYNTAX:trace@ - -Without a specified server it shows the current connections on -the local server. If you specify a remote server it will show -all servers between your current server and that remote server -as well as the connections on that remote server. - diff --git a/apps/irssi/docs/help/in/ts.in b/apps/irssi/docs/help/in/ts.in deleted file mode 100644 index 0f51f96d..00000000 --- a/apps/irssi/docs/help/in/ts.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:ts@ - -Shows topics of all channels you're on. - -See also: CHANNEL, TOPIC - diff --git a/apps/irssi/docs/help/in/umode.in b/apps/irssi/docs/help/in/umode.in new file mode 100644 index 00000000..a833a0d0 --- /dev/null +++ b/apps/irssi/docs/help/in/umode.in @@ -0,0 +1,15 @@ + +@SYNTAX:umode@ + +This command is used to manage client's modes in the network. +Note that some of the modes the client cannot set itself. +The mode is added by adding + before the option(s) and removed +by adding - before the option(s). The following channel user +modes are available: + + a Unset all modes + s Unset server operator privileges + r Unset router operator privileges + g Set/unset to be gone (or use /AWAY command) + +See also: CMODE, CUMODE, AWAY diff --git a/apps/irssi/docs/help/in/unban.in b/apps/irssi/docs/help/in/unban.in deleted file mode 100644 index a22963a1..00000000 --- a/apps/irssi/docs/help/in/unban.in +++ /dev/null @@ -1,11 +0,0 @@ - -@SYNTAX:unban@ - -Removes the specified ban(s) from the channel. - -Examples: - /UNBAN *!*@*.fi - /UNBAN larry!*@* *!me@*.mydomain.net - -See also: BAN, KNOCKOUT - diff --git a/apps/irssi/docs/help/in/unload.in b/apps/irssi/docs/help/in/unload.in index 4bfdeb95..2e1c4678 100644 --- a/apps/irssi/docs/help/in/unload.in +++ b/apps/irssi/docs/help/in/unload.in @@ -1,8 +1,8 @@ @SYNTAX:unload@ -Unload a running plugin. List of running plugins can be shown with -/LOAD. +Unload a running plugin. List of running plugins can +be shown with /LOAD. See also: LOAD diff --git a/apps/irssi/docs/help/in/unnotify.in b/apps/irssi/docs/help/in/unnotify.in deleted file mode 100644 index 5d74ce80..00000000 --- a/apps/irssi/docs/help/in/unnotify.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:unnotify@ - -Removes an entry from the notify list. - -See also: NOTIFY - diff --git a/apps/irssi/docs/help/in/unsilence.in b/apps/irssi/docs/help/in/unsilence.in deleted file mode 100644 index 110ff557..00000000 --- a/apps/irssi/docs/help/in/unsilence.in +++ /dev/null @@ -1,9 +0,0 @@ - -@SYNTAX:unsilence@ - -Works only in the Undernet and Open Projects (ircu). - -Removes a pattern from your silence list. - -See also: SILENCE - diff --git a/apps/irssi/docs/help/in/uping.in b/apps/irssi/docs/help/in/uping.in deleted file mode 100644 index 0fc003b8..00000000 --- a/apps/irssi/docs/help/in/uping.in +++ /dev/null @@ -1,12 +0,0 @@ - -@SYNTAX:uping@ - -IRC Operator command. Works only in the Undernet and Open Projects (ircu). - -This command works like the PING command (CTCP PING), except -it is used on a server instead of a client. As with PING, it -is used to test the relative distance another server is from -you across the irc network. - -See also: RPING, OPER - diff --git a/apps/irssi/docs/help/in/userhost.in b/apps/irssi/docs/help/in/userhost.in deleted file mode 100644 index a1039f9d..00000000 --- a/apps/irssi/docs/help/in/userhost.in +++ /dev/null @@ -1,7 +0,0 @@ - -@SYNTAX:userhost@ - -Shows the userhost info of the specified nick. - -See also: WHOIS - diff --git a/apps/irssi/docs/help/in/users.in b/apps/irssi/docs/help/in/users.in new file mode 100644 index 00000000..952a8825 --- /dev/null +++ b/apps/irssi/docs/help/in/users.in @@ -0,0 +1,11 @@ + +@SYNTAX:users@ + +Shows users of the specified channel. You must +already be on the channel. + +Alias WHO is by default USERS * command. + +See also: WHOIS, WHOWAS + + diff --git a/apps/irssi/docs/help/in/ver.in b/apps/irssi/docs/help/in/ver.in deleted file mode 100644 index 975fbf7a..00000000 --- a/apps/irssi/docs/help/in/ver.in +++ /dev/null @@ -1,9 +0,0 @@ - -@SYNTAX:ver@ - -Sends a CTCP VERSION request to the nick. This is used -to find out which client and/or script the nick -is using. - -See also: CTCP - diff --git a/apps/irssi/docs/help/in/version.in b/apps/irssi/docs/help/in/version.in index a7dd5c6f..d29a4cae 100644 --- a/apps/irssi/docs/help/in/version.in +++ b/apps/irssi/docs/help/in/version.in @@ -1,8 +1,6 @@ @SYNTAX:version@ -Shows the version info of the current or specified -IRC server. +Shows the version of the client. -See also: ADMIN, STATS diff --git a/apps/irssi/docs/help/in/voice.in b/apps/irssi/docs/help/in/voice.in deleted file mode 100644 index ee6f243e..00000000 --- a/apps/irssi/docs/help/in/voice.in +++ /dev/null @@ -1,8 +0,0 @@ - -@SYNTAX:voice@ - -Gives the voice (+v mode) to the nick(s) on the current channel. -Wildcards in the nick are allowed. - -See also: DEVOICE, OP, DEOP - diff --git a/apps/irssi/docs/help/in/wait.in b/apps/irssi/docs/help/in/wait.in deleted file mode 100644 index 4f5dc2f8..00000000 --- a/apps/irssi/docs/help/in/wait.in +++ /dev/null @@ -1,10 +0,0 @@ - -@SYNTAX:wait@ - -Wait for before sending the next command to server. - -This could be useful for example when identifying to NickServ; after -sending the identify message you'd wait 3 seconds before joining to -channels so NickServ has time to identify you to ChanServ which then -auto-ops you when joining. - diff --git a/apps/irssi/docs/help/in/wall.in b/apps/irssi/docs/help/in/wall.in deleted file mode 100644 index 212c3a8c..00000000 --- a/apps/irssi/docs/help/in/wall.in +++ /dev/null @@ -1,11 +0,0 @@ - -@SYNTAX:wall@ - -This command sends a message to all operators in a channel. This is internal -irssi command which sends a message separately to each opearator, so this -may not be very good idea to use in channel with lots of operators. - -Some IRC servers support also /MSG @#channel or /WALLCHOPS which you should -use if possible. - -See also: WALLCHOPS diff --git a/apps/irssi/docs/help/in/wallchops.in b/apps/irssi/docs/help/in/wallchops.in deleted file mode 100644 index 034b1f68..00000000 --- a/apps/irssi/docs/help/in/wallchops.in +++ /dev/null @@ -1,9 +0,0 @@ - -@SYNTAX:wallchops@ - -Works only in the Undernet and Open Projects (ircu). - -Sends an message to all other channel operators of the current channel. - -See also: WALL - diff --git a/apps/irssi/docs/help/in/wallops.in b/apps/irssi/docs/help/in/wallops.in deleted file mode 100644 index 1690db2c..00000000 --- a/apps/irssi/docs/help/in/wallops.in +++ /dev/null @@ -1,9 +0,0 @@ - -@SYNTAX:wallops@ - -This command sends the given message to everyone on -the network who has user mode +w turned on. If you -are not an operator, you will probably receive -an error message when using this command - -See also: OPER, WALLOPS diff --git a/apps/irssi/docs/help/in/who.in b/apps/irssi/docs/help/in/who.in deleted file mode 100644 index 014231ee..00000000 --- a/apps/irssi/docs/help/in/who.in +++ /dev/null @@ -1,23 +0,0 @@ - -@SYNTAX:who@ - -Without parameters, shows all users and their user infos -on the current channel. If you specify a channel, shows -the all users of the given channel. - -If you specify an string with wildcards, you will be -shown all users whose nick, userhost or realname matches -the wildcard expression. - -If a channel is secret or private and you're not on it, -you will be shown only those channel members, who do not -have the invisible (+i) mode set. - -Examples: - /WHO - Shows users on current channel - /WHO #irssi - Shows users on channel #irssi - /WHO timo* - Shows users whose nick, userhost, - or realname begins with string 'timo' - -See also: WHOIS, CHANNEL - diff --git a/apps/irssi/docs/help/in/whois.in b/apps/irssi/docs/help/in/whois.in index 55f3e330..f9ebe71d 100644 --- a/apps/irssi/docs/help/in/whois.in +++ b/apps/irssi/docs/help/in/whois.in @@ -1,15 +1,9 @@ @SYNTAX:whois@ -Shows whois information of the specified nick. +Shows whois information of the specified client. By default, this is aliased to /WI. -/WHOIS nick1 nick1 also queries the idle time of -the user. This is aliased to /WII by default. +See also: WHOWAS, CHANNEL -If given nick is not in the IRC, irssi automatically -sends a WHOWAS query. Read carefully the reply to see -if it is a WHOIS or WHOWAS reply. :) - -See also: WHO, CHANNEL diff --git a/apps/irssi/docs/help/in/whowas.in b/apps/irssi/docs/help/in/whowas.in index 2c7c68bc..65f8040e 100644 --- a/apps/irssi/docs/help/in/whowas.in +++ b/apps/irssi/docs/help/in/whowas.in @@ -1,19 +1,16 @@ @SYNTAX:whowas@ -This command is similar to WHOIS, except it returns information -about nicknames that were recently in use. Like WHOIS, it shows -the nickname, address, real name, and server. It may also return -multiple entries if the nickname has been used recently by several -people. These multiples may be limited by specifying a count to show. - -WHOWAS will work regardless of whether the queried nick is in use. -If no arguments are given, the client's current nickname is used. - -Example: To show the last 5 users of the nickname JoeBob: - -/whowas joebob 5 - +This command is similar to WHOIS, except it returns +information about nicknames that were recently in use. +Like WHOIS, it shows the nickname, address, real name, +and server. It may also return multiple entries if the +nickname has been used recently by several people. These +multiples may be limited by specifying a count to show. + +WHOWAS will work regardless of whether the queried nick +is in use. If no arguments are given, the client's current +nickname is used. See also: WHOIS diff --git a/apps/irssi/docs/help/in/wjoin.in b/apps/irssi/docs/help/in/wjoin.in deleted file mode 100644 index 8386e178..00000000 --- a/apps/irssi/docs/help/in/wjoin.in +++ /dev/null @@ -1,9 +0,0 @@ - -@SYNTAX:wjoin@ - -With this you can join multiple channels in same -window. This command does the same as /JOIN but -it doesn't create a new window for the channel joined. - -See also: JOIN, WINDOW - diff --git a/apps/irssi/docs/help/in/wquery.in b/apps/irssi/docs/help/in/wquery.in deleted file mode 100644 index ae4799af..00000000 --- a/apps/irssi/docs/help/in/wquery.in +++ /dev/null @@ -1,8 +0,0 @@ - -@SYNTAX:wquery@ - -Starts a query in the current window without -opening a new window. - -See also: QUERY, WINDOW, SET AUTOCREATE - diff --git a/apps/irssi/irssi-version.h.in b/apps/irssi/irssi-version.h.in deleted file mode 100644 index 668993fc..00000000 --- a/apps/irssi/irssi-version.h.in +++ /dev/null @@ -1,3 +0,0 @@ -/* automatically created by autogen.sh */ -#define IRSSI_VERSION "@VERSION@" -#define IRSSI_VERSION_DATE "20010524" diff --git a/apps/irssi/irssi.spec.in b/apps/irssi/irssi.spec.in index a3e9b9c0..40f1b21e 100644 --- a/apps/irssi/irssi.spec.in +++ b/apps/irssi/irssi.spec.in @@ -109,8 +109,8 @@ rm -rf $RPM_BUILD_ROOT All below listed persons can be reached on @pld.org.pl $Log$ -Revision 1.1.1.1 2001/05/24 12:09:28 priikone - imported irssi. +Revision 1.1 2001/05/24 12:09:28 priikone +Initial revision Revision 1.11 2001/03/29 14:38:28 cras http://irssi.org -> http://irssi.org/ diff --git a/apps/irssi/src/core/Makefile.am b/apps/irssi/src/core/Makefile.am new file mode 100644 index 00000000..1443bc91 --- /dev/null +++ b/apps/irssi/src/core/Makefile.am @@ -0,0 +1,104 @@ +noinst_LIBRARIES = libcore.a + +include $(top_srcdir)/Makefile.defines.in + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -DSYSCONFDIR=\""$(silc_etcdir)"\" \ + -DMODULEDIR=\""$(silc_modulesdir)"\" \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/core + +if BUILD_MEMDEBUG +memdebug_src=memdebug.c +else +memdebug_src= +endif + +libcore_a_SOURCES = \ + args.c \ + channels.c \ + channels-setup.c \ + commands.c \ + chat-commands.c \ + chat-protocols.c \ + chatnets.c \ + core.c \ + expandos.c \ + ignore.c \ + levels.c \ + line-split.c \ + log.c \ + masks.c \ + $(memdebug_src) \ + misc.c \ + modules.c \ + net-disconnect.c \ + net-nonblock.c \ + net-sendbuffer.c \ + network.c \ + nicklist.c \ + nickmatch-cache.c \ + pidwait.c \ + queries.c \ + rawlog.c \ + servers.c \ + servers-reconnect.c \ + servers-redirect.c \ + servers-setup.c \ + settings.c \ + signals.c \ + special-vars.c \ + write-buffer.c + +structure_headers = \ + channel-rec.h \ + channel-setup-rec.h \ + chatnet-rec.h \ + query-rec.h \ + server-rec.h \ + server-setup-rec.h \ + server-connect-rec.h \ + window-item-rec.h + +noinst_HEADERS = \ + args.h \ + channels.h \ + channels-setup.h \ + commands.h \ + chat-protocols.h \ + chatnets.h \ + core.h \ + expandos.h \ + ignore.h \ + levels.h \ + line-split.h \ + log.h \ + masks.h \ + memdebug.h \ + misc.h \ + module.h \ + modules.h \ + net-disconnect.h \ + net-nonblock.h \ + net-sendbuffer.h \ + network.h \ + nick-rec.h \ + nicklist.h \ + nickmatch-cache.h \ + pidwait.h \ + queries.h \ + rawlog.h \ + servers.h \ + servers-reconnect.h \ + servers-redirect.h \ + servers-setup.h \ + settings.h \ + signals.h \ + special-vars.h \ + window-item-def.h \ + write-buffer.h \ + $(structure_headers) + +EXTRA_DIST = \ + memdebug.c diff --git a/apps/irssi/src/core/args.c b/apps/irssi/src/core/args.c new file mode 100644 index 00000000..093a8d56 --- /dev/null +++ b/apps/irssi/src/core/args.c @@ -0,0 +1,64 @@ +/* + args.c : small frontend to libPopt command line argument parser + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "args.h" + +GArray *iopt_tables = NULL; + +void args_register(struct poptOption *options) +{ + if (iopt_tables == NULL) { + iopt_tables = g_array_new(TRUE, TRUE, + sizeof(struct poptOption)); + } + + while (options->longName != NULL || options->shortName != '\0' || + options->arg != NULL) { + g_array_append_val(iopt_tables, *options); + options = options+1; + } +} + +void args_execute(int argc, char *argv[]) +{ + poptContext con; + int nextopt; + + if (iopt_tables == NULL) + return; + + con = poptGetContext(PACKAGE, argc, argv, + (struct poptOption *) (iopt_tables->data), 0); + poptReadDefaultConfig(con, TRUE); + + while ((nextopt = poptGetNextOpt(con)) > 0) ; + + if (nextopt != -1) { + printf("Error on option %s: %s.\n" + "Run '%s --help' to see a full list of " + "available command line options.\n", + poptBadOption(con, 0), poptStrerror(nextopt), argv[0]); + exit(1); + } + + g_array_free(iopt_tables, TRUE); + iopt_tables = NULL; +} diff --git a/apps/irssi/src/core/args.h b/apps/irssi/src/core/args.h new file mode 100644 index 00000000..4d1b82fb --- /dev/null +++ b/apps/irssi/src/core/args.h @@ -0,0 +1,15 @@ +#ifndef __ARGS_H +#define __ARGS_H + +#ifdef HAVE_POPT_H +# include +#else +# include "lib-popt/popt.h" +#endif + +extern GArray *iopt_tables; + +void args_register(struct poptOption *options); +void args_execute(int argc, char *argv[]); + +#endif diff --git a/apps/irssi/src/core/channel-rec.h b/apps/irssi/src/core/channel-rec.h new file mode 100644 index 00000000..2ff6a536 --- /dev/null +++ b/apps/irssi/src/core/channel-rec.h @@ -0,0 +1,30 @@ +/* CHANNEL_REC definition, used for inheritance */ + +#include "window-item-rec.h" + +char *topic; +char *topic_by; +time_t topic_time; + +GHashTable *nicks; /* list of nicks */ +NICK_REC *ownnick; /* our own nick */ + +unsigned int no_modes:1; /* channel doesn't support modes */ +char *mode; +int limit; /* user limit */ +char *key; /* password key */ + +unsigned int chanop:1; /* You're a channel operator */ +unsigned int names_got:1; /* Received /NAMES list */ +unsigned int wholist:1; /* WHO list got */ +unsigned int synced:1; /* Channel synced - all queries done */ + +unsigned int joined:1; /* Have we even received JOIN event for this channel? */ +unsigned int left:1; /* You just left the channel */ +unsigned int kicked:1; /* You just got kicked */ +unsigned int destroying:1; + +/* Return the information needed to call SERVER_REC->channels_join() for + this channel. Usually just the channel name, but may contain also the + channel key. */ +char *(*get_join_data)(CHANNEL_REC *channel); diff --git a/apps/irssi/src/core/channel-setup-rec.h b/apps/irssi/src/core/channel-setup-rec.h new file mode 100644 index 00000000..a0b28978 --- /dev/null +++ b/apps/irssi/src/core/channel-setup-rec.h @@ -0,0 +1,12 @@ +int type; /* module_get_uniq_id("CHANNEL SETUP", 0) */ +int chat_type; /* chat_protocol_lookup(xx) */ + +char *name; +char *chatnet; +char *password; + +char *botmasks; +char *autosendcmd; + +unsigned int autojoin:1; +GHashTable *module_data; diff --git a/apps/irssi/src/core/channels-setup.c b/apps/irssi/src/core/channels-setup.c new file mode 100644 index 00000000..a53bd3f3 --- /dev/null +++ b/apps/irssi/src/core/channels-setup.c @@ -0,0 +1,182 @@ +/* + channels-setup.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "chatnets.h" +#include "servers-setup.h" +#include "channels-setup.h" + +GSList *setupchannels; + +static void channel_setup_save(CHANNEL_SETUP_REC *channel) +{ + CONFIG_NODE *parentnode, *node; + int index; + + index = g_slist_index(setupchannels, channel); + + parentnode = iconfig_node_traverse("(channels", TRUE); + node = config_node_index(parentnode, index); + if (node == NULL) + node = config_node_section(parentnode, NULL, NODE_TYPE_BLOCK); + + iconfig_node_clear(node); + iconfig_node_set_str(node, "name", channel->name); + iconfig_node_set_str(node, "chatnet", channel->chatnet); + if (channel->autojoin) + iconfig_node_set_bool(node, "autojoin", TRUE); + iconfig_node_set_str(node, "password", channel->password); + iconfig_node_set_str(node, "botmasks", channel->botmasks); + iconfig_node_set_str(node, "autosendcmd", channel->autosendcmd); +} + +void channel_setup_create(CHANNEL_SETUP_REC *channel) +{ + if (g_slist_find(setupchannels, channel) == NULL) + setupchannels = g_slist_append(setupchannels, channel); + channel_setup_save(channel); + + signal_emit("channel setup created", 1, channel); +} + +static void channel_config_remove(CHANNEL_SETUP_REC *channel) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("channels", FALSE); + if (node != NULL) iconfig_node_list_remove(node, g_slist_index(setupchannels, channel)); +} + +static void channel_setup_destroy(CHANNEL_SETUP_REC *channel) +{ + g_return_if_fail(channel != NULL); + + setupchannels = g_slist_remove(setupchannels, channel); + signal_emit("channel setup destroyed", 1, channel); + + g_free_not_null(channel->chatnet); + g_free_not_null(channel->password); + g_free_not_null(channel->botmasks); + g_free_not_null(channel->autosendcmd); + g_free(channel->name); + g_free(channel); +} + +void channel_setup_remove(CHANNEL_SETUP_REC *channel) +{ + channel_config_remove(channel); + channel_setup_destroy(channel); +} + +CHANNEL_SETUP_REC *channel_setup_find(const char *channel, + const char *chatnet) +{ + GSList *tmp; + + g_return_val_if_fail(channel != NULL, NULL); + + for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) { + CHANNEL_SETUP_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, channel) == 0 && + channel_chatnet_match(rec->chatnet, chatnet)) + return rec; + } + + return NULL; +} + +static CHANNEL_SETUP_REC *channel_setup_read(CONFIG_NODE *node) +{ + CHANNEL_SETUP_REC *rec; + CHATNET_REC *chatnetrec; + char *channel, *chatnet; + + g_return_val_if_fail(node != NULL, NULL); + + channel = config_node_get_str(node, "name", NULL); + chatnet = config_node_get_str(node, "chatnet", NULL); + if (chatnet == NULL) /* FIXME: remove this after .98... */ { + chatnet = g_strdup(config_node_get_str(node, "ircnet", NULL)); + if (chatnet != NULL) { + iconfig_node_set_str(node, "chatnet", chatnet); + iconfig_node_set_str(node, "ircnet", NULL); + } + } + + chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet); + if (channel == NULL || chatnetrec == NULL) { + /* missing information.. */ + return NULL; + } + + rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup(); + rec->type = module_get_uniq_id("CHANNEL SETUP", 0); + rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id; + rec->autojoin = config_node_get_bool(node, "autojoin", FALSE); + rec->name = g_strdup(channel); + rec->chatnet = g_strdup(chatnetrec != NULL ? chatnetrec->name : chatnet); + rec->password = g_strdup(config_node_get_str(node, "password", NULL)); + rec->botmasks = g_strdup(config_node_get_str(node, "botmasks", NULL)); + rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL)); + + setupchannels = g_slist_append(setupchannels, rec); + signal_emit("channel setup created", 2, rec, node); + return rec; +} + +static void channels_read_config(void) +{ + CONFIG_NODE *node; + GSList *tmp; + + while (setupchannels != NULL) + channel_setup_destroy(setupchannels->data); + + /* Read channels */ + node = iconfig_node_traverse("channels", FALSE); + if (node != NULL) { + for (tmp = node->value; tmp != NULL; tmp = tmp->next) + channel_setup_read(tmp->data); + } +} + +void channels_setup_init(void) +{ + setupchannels = NULL; + source_host_ok = FALSE; + + signal_add("setup reread", (SIGNAL_FUNC) channels_read_config); + signal_add("irssi init read settings", (SIGNAL_FUNC) channels_read_config); +} + +void channels_setup_deinit(void) +{ + while (setupchannels != NULL) + channel_setup_destroy(setupchannels->data); + + signal_remove("setup reread", (SIGNAL_FUNC) channels_read_config); + signal_remove("irssi init read settings", (SIGNAL_FUNC) channels_read_config); +} diff --git a/apps/irssi/src/core/channels-setup.h b/apps/irssi/src/core/channels-setup.h new file mode 100644 index 00000000..423bccb2 --- /dev/null +++ b/apps/irssi/src/core/channels-setup.h @@ -0,0 +1,31 @@ +#ifndef __CHANNELS_SETUP_H +#define __CHANNELS_SETUP_H + +#include "modules.h" + +#define CHANNEL_SETUP(server) \ + MODULE_CHECK_CAST(server, CHANNEL_SETUP_REC, type, "CHANNEL SETUP") + +#define IS_CHANNEL_SETUP(server) \ + (CHANNEL_SETUP(server) ? TRUE : FALSE) + +struct _CHANNEL_SETUP_REC { +#include "channel-setup-rec.h" +}; + +extern GSList *setupchannels; + +void channels_setup_init(void); +void channels_setup_deinit(void); + +void channel_setup_create(CHANNEL_SETUP_REC *channel); +void channel_setup_remove(CHANNEL_SETUP_REC *channel); + +CHANNEL_SETUP_REC *channel_setup_find(const char *channel, + const char *chatnet); + +#define channel_chatnet_match(rec, chatnet) \ + ((rec) == NULL || (rec)[0] == '\0' || \ + ((chatnet) != NULL && g_strcasecmp(rec, chatnet) == 0)) + +#endif diff --git a/apps/irssi/src/core/channels.c b/apps/irssi/src/core/channels.c new file mode 100644 index 00000000..f3d3a7a4 --- /dev/null +++ b/apps/irssi/src/core/channels.c @@ -0,0 +1,209 @@ +/* + channel.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "special-vars.h" + +#include "servers.h" +#include "channels.h" +#include "channels-setup.h" +#include "nicklist.h" + +GSList *channels; /* List of all channels */ + +static char *get_join_data(CHANNEL_REC *channel) +{ + return g_strdup(channel->name); +} + +void channel_init(CHANNEL_REC *channel, int automatic) +{ + g_return_if_fail(channel != NULL); + g_return_if_fail(channel->name != NULL); + + channels = g_slist_append(channels, channel); + if (channel->server != NULL) { + channel->server->channels = + g_slist_append(channel->server->channels, channel); + } + + MODULE_DATA_INIT(channel); + channel->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "CHANNEL"); + channel->mode = g_strdup(""); + channel->createtime = time(NULL); + channel->get_join_data = get_join_data; + + signal_emit("channel created", 2, channel, GINT_TO_POINTER(automatic)); +} + +void channel_destroy(CHANNEL_REC *channel) +{ + g_return_if_fail(IS_CHANNEL(channel)); + + if (channel->destroying) return; + channel->destroying = TRUE; + + channels = g_slist_remove(channels, channel); + if (channel->server != NULL) + channel->server->channels = g_slist_remove(channel->server->channels, channel); + signal_emit("channel destroyed", 1, channel); + + MODULE_DATA_DEINIT(channel); + g_free_not_null(channel->hilight_color); + g_free_not_null(channel->topic); + g_free_not_null(channel->topic_by); + g_free_not_null(channel->key); + g_free(channel->mode); + g_free(channel->name); + g_free(channel); +} + +static CHANNEL_REC *channel_find_server(SERVER_REC *server, + const char *name) +{ + GSList *tmp; + + g_return_val_if_fail(IS_SERVER(server), NULL); + + if (server->channel_find_func != NULL) { + /* use the server specific channel find function */ + return server->channel_find_func(server, name); + } + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (g_strcasecmp(name, rec->name) == 0) + return rec; + } + + return NULL; +} + +CHANNEL_REC *channel_find(SERVER_REC *server, const char *name) +{ + g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL); + g_return_val_if_fail(name != NULL, NULL); + + if (server != NULL) + return channel_find_server(server, name); + + /* find from any server */ + return gslist_foreach_find(servers, + (FOREACH_FIND_FUNC) channel_find_server, + (void *) name); +} + +/* connected to server, autojoin to channels. */ +static void event_connected(SERVER_REC *server) +{ + GString *chans; + GSList *tmp; + + g_return_if_fail(SERVER(server)); + + if (server->connrec->reconnection) + return; + + /* join to the channels marked with autojoin in setup */ + chans = g_string_new(NULL); + for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) { + CHANNEL_SETUP_REC *rec = tmp->data; + + if (!rec->autojoin || + !channel_chatnet_match(rec->chatnet, + server->connrec->chatnet)) + continue; + + g_string_sprintfa(chans, "%s,", rec->name); + } + + if (chans->len > 0) { + g_string_truncate(chans, chans->len-1); + server->channels_join(server, chans->str, TRUE); + } + + g_string_free(chans, TRUE); +} + +static int match_nick_flags(SERVER_REC *server, NICK_REC *nick, char flag) +{ + const char *flags = server->get_nick_flags(); + + return strchr(flags, flag) == NULL || + (flag == flags[0] && nick->op) || + (flag == flags[1] && (nick->voice || nick->halfop || + nick->op)) || + (flag == flags[2] && (nick->halfop || nick->op)); +} + +/* Send the auto send command to channel */ +void channel_send_autocommands(CHANNEL_REC *channel) +{ + CHANNEL_SETUP_REC *rec; + NICK_REC *nick; + char **bots, **bot; + + g_return_if_fail(IS_CHANNEL(channel)); + + rec = channel_setup_find(channel->name, channel->server->connrec->chatnet); + if (rec == NULL || rec->autosendcmd == NULL || !*rec->autosendcmd) + return; + + if (rec->botmasks == NULL || !*rec->botmasks) { + /* just send the command. */ + eval_special_string(rec->autosendcmd, "", channel->server, channel); + return; + } + + /* find first available bot.. */ + bots = g_strsplit(rec->botmasks, " ", -1); + for (bot = bots; *bot != NULL; bot++) { + const char *botnick = *bot; + + nick = nicklist_find_mask(channel, + channel->server->isnickflag(*botnick) ? + botnick+1 : botnick); + if (nick != NULL && + match_nick_flags(channel->server, nick, *botnick)) { + eval_special_string(rec->autosendcmd, nick->nick, + channel->server, channel); + break; + } + } + g_strfreev(bots); +} + +void channels_init(void) +{ + channels_setup_init(); + + signal_add("event connected", (SIGNAL_FUNC) event_connected); +} + +void channels_deinit(void) +{ + channels_setup_deinit(); + + signal_remove("event connected", (SIGNAL_FUNC) event_connected); + module_uniq_destroy("CHANNEL"); +} diff --git a/apps/irssi/src/core/channels.h b/apps/irssi/src/core/channels.h new file mode 100644 index 00000000..98b75ee0 --- /dev/null +++ b/apps/irssi/src/core/channels.h @@ -0,0 +1,34 @@ +#ifndef __CHANNELS_H +#define __CHANNELS_H + +#include "modules.h" + +/* Returns CHANNEL_REC if it's channel, NULL if it isn't. */ +#define CHANNEL(channel) \ + MODULE_CHECK_CAST_MODULE(channel, CHANNEL_REC, type, \ + "WINDOW ITEM TYPE", "CHANNEL") + +#define IS_CHANNEL(channel) \ + (CHANNEL(channel) ? TRUE : FALSE) + +#define STRUCT_SERVER_REC SERVER_REC +struct _CHANNEL_REC { +#include "channel-rec.h" +}; + +extern GSList *channels; + +/* Create new channel record */ +void channel_init(CHANNEL_REC *channel, int automatic); +void channel_destroy(CHANNEL_REC *channel); + +/* find channel by name, if `server' is NULL, search from all servers */ +CHANNEL_REC *channel_find(SERVER_REC *server, const char *name); + +/* Send the auto send command to channel */ +void channel_send_autocommands(CHANNEL_REC *channel); + +void channels_init(void); +void channels_deinit(void); + +#endif diff --git a/apps/irssi/src/core/chat-commands.c b/apps/irssi/src/core/chat-commands.c new file mode 100644 index 00000000..78a05a70 --- /dev/null +++ b/apps/irssi/src/core/chat-commands.c @@ -0,0 +1,384 @@ +/* + chat-commands.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" +#include "signals.h" +#include "commands.h" +#include "special-vars.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "servers.h" +#include "servers-setup.h" +#include "servers-reconnect.h" +#include "channels.h" +#include "queries.h" +#include "window-item-def.h" + +static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr) +{ + CHAT_PROTOCOL_REC *proto; + SERVER_CONNECT_REC *conn; + GHashTable *optlist; + char *addr, *portstr, *password, *nick, *chatnet, *host; + void *free_arg; + + g_return_val_if_fail(data != NULL, NULL); + + if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTIONS, + "connect", &optlist, &addr, &portstr, + &password, &nick)) + return NULL; + if (plus_addr != NULL) *plus_addr = *addr == '+'; + if (*addr == '+') addr++; + if (*addr == '\0') { + signal_emit("error command", 1, + GINT_TO_POINTER(CMDERR_NOT_ENOUGH_PARAMS)); + cmd_params_free(free_arg); + return NULL; + } + + if (strcmp(password, "-") == 0) + *password = '\0'; + + /* check if - option is used to specify chat protocol */ + proto = chat_protocol_find_net(optlist); + + /* connect to server */ + chatnet = proto == NULL ? NULL : + g_hash_table_lookup(optlist, proto->chatnet); + conn = server_create_conn(proto != NULL ? proto->id : -1, addr, + atoi(portstr), chatnet, password, nick); + if (proto == NULL) + proto = chat_protocol_find_id(conn->chat_type); + + if (proto->not_initialized) { + /* trying to use protocol that isn't yet initialized */ + signal_emit("chat protocol unknown", 1, proto->name); + server_connect_free(conn); + cmd_params_free(free_arg); + return NULL; + } + + if (g_hash_table_lookup(optlist, "6") != NULL) + conn->family = AF_INET6; + else if (g_hash_table_lookup(optlist, "4") != NULL) + conn->family = AF_INET; + + host = g_hash_table_lookup(optlist, "host"); + if (host != NULL && *host != '\0') { + IPADDR ip4, ip6; + + if (net_gethostbyname(host, &ip4, &ip6) == 0) + server_connect_own_ip_save(conn, &ip4, &ip6); + } + + cmd_params_free(free_arg); + return conn; +} + +/* SYNTAX: CONNECT [-4 | -6] [-ircnet ] [-host ] +
| [ [ []]] */ +static void cmd_connect(const char *data) +{ + SERVER_CONNECT_REC *conn; + + conn = get_server_connect(data, NULL); + if (conn != NULL) + CHAT_PROTOCOL(conn)->server_connect(conn); +} + +static RECONNECT_REC *find_reconnect_server(int chat_type, + const char *addr, int port) +{ + RECONNECT_REC *match, *last_proto_match; + GSList *tmp; + int count; + + g_return_val_if_fail(addr != NULL, NULL); + + /* check if there's a reconnection to the same host and maybe even + the same port */ + match = last_proto_match = NULL; count = 0; + for (tmp = reconnects; tmp != NULL; tmp = tmp->next) { + RECONNECT_REC *rec = tmp->data; + + if (rec->conn->chat_type == chat_type) { + count++; last_proto_match = rec; + if (g_strcasecmp(rec->conn->address, addr) == 0) { + if (rec->conn->port == port) + return rec; + match = rec; + } + } + } + + if (count == 1) { + /* only one reconnection with wanted protocol, + we probably want to use it */ + return last_proto_match; + } + + return match; +} + +static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server) +{ + SERVER_CONNECT_REC *oldconn; + RECONNECT_REC *recon; + + if (server != NULL) { + oldconn = server->connrec; + reconnect_save_status(conn, server); + } else { + /* maybe we can reconnect some server from + reconnection queue */ + recon = find_reconnect_server(conn->chat_type, + conn->address, conn->port); + if (recon == NULL) return; + + oldconn = recon->conn; + server_reconnect_destroy(recon, FALSE); + + conn->away_reason = g_strdup(oldconn->away_reason); + conn->channels = g_strdup(oldconn->channels); + } + + conn->reconnection = TRUE; + + if (conn->chatnet == NULL && oldconn->chatnet != NULL) + conn->chatnet = g_strdup(oldconn->chatnet); + + if (server != NULL) { + signal_emit("command disconnect", 2, + "* Changing server", server); + } else { + server_connect_free(oldconn); + } +} + +/* SYNTAX: SERVER [-4 | -6] [-ircnet ] [-host ] + [+]
| [ [ []]] */ +static void cmd_server(const char *data, SERVER_REC *server) +{ + SERVER_CONNECT_REC *conn; + int plus_addr; + + g_return_if_fail(data != NULL); + + /* create connection record */ + conn = get_server_connect(data, &plus_addr); + if (conn != NULL) { + if (!plus_addr) + update_reconnection(conn, server); + CHAT_PROTOCOL(conn)->server_connect(conn); + } +} + +/* SYNTAX: DISCONNECT *| [] */ +static void cmd_disconnect(const char *data, SERVER_REC *server) +{ + char *tag, *msg; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg)) + return; + + if (*tag != '\0' && strcmp(tag, "*") != 0) + server = server_find_tag(tag); + if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED); + + if (*msg == '\0') msg = (char *) settings_get_str("quit_message"); + signal_emit("server quit", 2, server, msg); + + cmd_params_free(free_arg); + server_disconnect(server); +} + +/* SYNTAX: QUIT [] */ +static void cmd_quit(const char *data) +{ + GSList *tmp, *next; + const char *quitmsg; + char *str; + + g_return_if_fail(data != NULL); + + quitmsg = *data != '\0' ? data : + settings_get_str("quit_message"); + + /* disconnect from every server */ + for (tmp = servers; tmp != NULL; tmp = next) { + next = tmp->next; + + str = g_strdup_printf("* %s", quitmsg); + cmd_disconnect(str, tmp->data); + g_free(str); + } + + signal_emit("gui exit", 0); +} + +/* SYNTAX: JOIN [-invite] [-] [] */ +static void cmd_join(const char *data, SERVER_REC *server) +{ + GHashTable *optlist; + char *channels; + void *free_arg; + + g_return_if_fail(data != NULL); + if (!IS_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, + "join", &optlist, &channels)) + return; + + if (g_hash_table_lookup(optlist, "invite")) + channels = server->last_invite; + else { + if (*channels == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + /* - */ + server = cmd_options_get_server("join", optlist, server); + } + + if (server != NULL && channels != NULL) + server->channels_join(server, channels, FALSE); + cmd_params_free(free_arg); +} + +/* SYNTAX: MSG [-] */ +static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + GHashTable *optlist; + char *target, *origtarget, *msg; + void *free_arg; + int free_ret; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, + "msg", &optlist, &target, &msg)) + return; + if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + server = cmd_options_get_server("msg", optlist, SERVER(server)); + if (server == NULL || !server->connected) + cmd_param_error(CMDERR_NOT_CONNECTED); + + origtarget = target; + free_ret = FALSE; + if (strcmp(target, ",") == 0 || strcmp(target, ".") == 0) { + target = parse_special(&target, server, item, + NULL, &free_ret, NULL, 0); + if (target != NULL && *target == '\0') + target = NULL; + } else if (strcmp(target, "*") == 0 && item != NULL) + target = item->name; + + if (target != NULL) + server->send_message(server, target, msg); + + signal_emit(target != NULL && server->ischannel(target) ? + "message own_public" : "message own_private", 4, + server, msg, target, origtarget); + + if (free_ret && target != NULL) g_free(target); + cmd_params_free(free_arg); +} + +static void cmd_foreach(const char *data, SERVER_REC *server, + WI_ITEM_REC *item) +{ + command_runsub("foreach", data, server, item); +} + +/* SYNTAX: FOREACH SERVER */ +static void cmd_foreach_server(const char *data, SERVER_REC *server) +{ + GSList *tmp; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) + signal_emit("send command", 3, data, tmp->data, NULL); +} + +/* SYNTAX: FOREACH CHANNEL */ +static void cmd_foreach_channel(const char *data) +{ + GSList *tmp; + + for (tmp = channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + signal_emit("send command", 3, data, rec->server, rec); + } +} + +/* SYNTAX: FOREACH QUERY */ +static void cmd_foreach_query(const char *data) +{ + GSList *tmp; + + for (tmp = queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + signal_emit("send command", 3, data, rec->server, rec); + } +} + +void chat_commands_init(void) +{ + settings_add_str("misc", "quit_message", "leaving"); + + command_bind("server", NULL, (SIGNAL_FUNC) cmd_server); + command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect); + command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect); + command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit); + command_bind("join", NULL, (SIGNAL_FUNC) cmd_join); + command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg); + command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach); + command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server); + command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel); + command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query); + + command_set_options("connect", "4 6 +host"); + command_set_options("join", "invite"); +} + +void chat_commands_deinit(void) +{ + command_unbind("server", (SIGNAL_FUNC) cmd_server); + command_unbind("connect", (SIGNAL_FUNC) cmd_connect); + command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect); + command_unbind("quit", (SIGNAL_FUNC) cmd_quit); + command_unbind("join", (SIGNAL_FUNC) cmd_join); + command_unbind("msg", (SIGNAL_FUNC) cmd_msg); + command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach); + command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server); + command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel); + command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query); +} diff --git a/apps/irssi/src/core/chat-protocols.c b/apps/irssi/src/core/chat-protocols.c new file mode 100644 index 00000000..ab7c09d4 --- /dev/null +++ b/apps/irssi/src/core/chat-protocols.c @@ -0,0 +1,228 @@ +/* + chat-protocol.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "signals.h" +#include "chat-protocols.h" + +#include "chatnets.h" +#include "servers.h" +#include "servers-setup.h" +#include "channels-setup.h" + +GSList *chat_protocols; + +static CHAT_PROTOCOL_REC *default_proto; + +void *chat_protocol_check_cast(void *object, int type_pos, const char *id) +{ + return object == NULL || + chat_protocol_lookup(id) != + G_STRUCT_MEMBER(int, object, type_pos) ? NULL : object; +} + +/* Return the ID for the specified chat protocol. */ +int chat_protocol_lookup(const char *name) +{ + CHAT_PROTOCOL_REC *rec; + + g_return_val_if_fail(name != NULL, -1); + + rec = chat_protocol_find(name); + return rec == NULL ? -1 : rec->id; +} + +CHAT_PROTOCOL_REC *chat_protocol_find(const char *name) +{ + GSList *tmp; + + g_return_val_if_fail(name != NULL, NULL); + + for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) { + CHAT_PROTOCOL_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +CHAT_PROTOCOL_REC *chat_protocol_find_id(int id) +{ + GSList *tmp; + + g_return_val_if_fail(id > 0, NULL); + + for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) { + CHAT_PROTOCOL_REC *rec = tmp->data; + + if (rec->id == id) + return rec; + } + + return NULL; +} + +CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist) +{ + GSList *tmp; + + g_return_val_if_fail(optlist != NULL, NULL); + + for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) { + CHAT_PROTOCOL_REC *rec = tmp->data; + + if (rec->chatnet != NULL && + g_hash_table_lookup(optlist, rec->chatnet) != NULL) + return rec; + } + + return NULL; +} + +/* Register new chat protocol. */ +CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec) +{ + CHAT_PROTOCOL_REC *newrec; + int created; + + g_return_val_if_fail(rec != NULL, NULL); + + newrec = chat_protocol_find(rec->name); + created = newrec == NULL; + if (newrec == NULL) { + newrec = g_new0(CHAT_PROTOCOL_REC, 1); + chat_protocols = g_slist_append(chat_protocols, newrec); + } else { + /* updating existing protocol */ + g_free(newrec->name); + } + + memcpy(newrec, rec, sizeof(CHAT_PROTOCOL_REC)); + newrec->id = module_get_uniq_id_str("PROTOCOL", rec->name); + newrec->name = g_strdup(rec->name); + + if (default_proto == NULL) + chat_protocol_set_default(newrec); + + if (created) + signal_emit("chat protocol created", 1, newrec); + else + signal_emit("chat protocol updated", 1, newrec); + return newrec; +} + +static void chat_protocol_destroy(CHAT_PROTOCOL_REC *rec) +{ + g_return_if_fail(rec != NULL); + + chat_protocols = g_slist_remove(chat_protocols, rec); + + if (default_proto == rec) { + chat_protocol_set_default(chat_protocols == NULL ? NULL : + chat_protocols->data); + } + + signal_emit("chat protocol destroyed", 1, rec); + + g_free(rec->name); + g_free(rec); +} + +/* Unregister chat protocol. */ +void chat_protocol_unregister(const char *name) +{ + CHAT_PROTOCOL_REC *rec; + + g_return_if_fail(name != NULL); + + rec = chat_protocol_find(name); + if (rec != NULL) chat_protocol_destroy(rec); +} + +/* Default chat protocol to use */ +void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec) +{ + default_proto = rec; +} + +CHAT_PROTOCOL_REC *chat_protocol_get_default(void) +{ + return default_proto; +} + +static CHATNET_REC *create_chatnet(void) +{ + return g_new0(CHATNET_REC, 1); +} + +static SERVER_SETUP_REC *create_server_setup(void) +{ + return g_new0(SERVER_SETUP_REC, 1); +} + +static CHANNEL_SETUP_REC *create_channel_setup(void) +{ + return g_new0(CHANNEL_SETUP_REC, 1); +} + +static SERVER_CONNECT_REC *create_server_connect(void) +{ + return g_new0(SERVER_CONNECT_REC, 1); +} + +/* Return "unknown chat protocol" record. Used when protocol name is + specified but it isn't registered yet. */ +CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name) +{ + CHAT_PROTOCOL_REC *rec, *newrec; + + g_return_val_if_fail(name != NULL, NULL); + + rec = chat_protocol_find(name); + if (rec != NULL) + return rec; + + rec = g_new0(CHAT_PROTOCOL_REC, 1); + rec->not_initialized = TRUE; + rec->name = (char *) name; + rec->create_chatnet = create_chatnet; + rec->create_server_setup = create_server_setup; + rec->create_channel_setup = create_channel_setup; + rec->create_server_connect = create_server_connect; + + newrec = chat_protocol_register(rec); + g_free(rec); + return newrec; +} + +void chat_protocols_init(void) +{ + default_proto = NULL; + chat_protocols = NULL; +} + +void chat_protocols_deinit(void) +{ + while (chat_protocols != NULL) + chat_protocol_destroy(chat_protocols->data); +} diff --git a/apps/irssi/src/core/chat-protocols.h b/apps/irssi/src/core/chat-protocols.h new file mode 100644 index 00000000..cc7eaadc --- /dev/null +++ b/apps/irssi/src/core/chat-protocols.h @@ -0,0 +1,57 @@ +#ifndef __CHAT_PROTOCOLS_H +#define __CHAT_PROTOCOLS_H + +typedef struct { + int id; + + unsigned int not_initialized:1; + + char *name; + char *fullname; + char *chatnet; + + CHATNET_REC *(*create_chatnet) (void); + SERVER_SETUP_REC *(*create_server_setup) (void); + CHANNEL_SETUP_REC *(*create_channel_setup) (void); + SERVER_CONNECT_REC *(*create_server_connect) (void); + + SERVER_REC *(*server_connect) (SERVER_CONNECT_REC *); + CHANNEL_REC *(*channel_create) (SERVER_REC *, const char *, int); + QUERY_REC *(*query_create) (const char *, const char *, int); +} CHAT_PROTOCOL_REC; + +extern GSList *chat_protocols; + +#define PROTO_CHECK_CAST(object, cast, type_field, id) \ + ((cast *) chat_protocol_check_cast(object, \ + offsetof(cast, type_field), id)) +void *chat_protocol_check_cast(void *object, int type_pos, const char *id); + +#define CHAT_PROTOCOL(object) \ + ((object) == NULL ? chat_protocol_get_default() : \ + chat_protocol_find_id((object)->chat_type)) + +/* Register new chat protocol. */ +CHAT_PROTOCOL_REC *chat_protocol_register(CHAT_PROTOCOL_REC *rec); + +/* Unregister chat protocol. */ +void chat_protocol_unregister(const char *name); + +/* Find functions */ +int chat_protocol_lookup(const char *name); +CHAT_PROTOCOL_REC *chat_protocol_find(const char *name); +CHAT_PROTOCOL_REC *chat_protocol_find_id(int id); +CHAT_PROTOCOL_REC *chat_protocol_find_net(GHashTable *optlist); + +/* Default chat protocol to use */ +void chat_protocol_set_default(CHAT_PROTOCOL_REC *rec); +CHAT_PROTOCOL_REC *chat_protocol_get_default(void); + +/* Return "unknown chat protocol" record. Used when protocol name is + specified but it isn't registered yet. */ +CHAT_PROTOCOL_REC *chat_protocol_get_unknown(const char *name); + +void chat_protocols_init(void); +void chat_protocols_deinit(void); + +#endif diff --git a/apps/irssi/src/core/chatnet-rec.h b/apps/irssi/src/core/chatnet-rec.h new file mode 100644 index 00000000..e3ed8aa0 --- /dev/null +++ b/apps/irssi/src/core/chatnet-rec.h @@ -0,0 +1,12 @@ +int type; /* module_get_uniq_id("CHATNET", 0) */ +int chat_type; /* chat_protocol_lookup(xx) */ + +char *name; + +char *nick; +char *username; +char *realname; + +char *own_host; /* address to use when connecting this server */ +char *autosendcmd; /* command to send after connecting to this ircnet */ +IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */ diff --git a/apps/irssi/src/core/chatnets.c b/apps/irssi/src/core/chatnets.c new file mode 100644 index 00000000..a2cff529 --- /dev/null +++ b/apps/irssi/src/core/chatnets.c @@ -0,0 +1,203 @@ +/* + chatnets.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" +#include "signals.h" +#include "special-vars.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "chatnets.h" +#include "servers.h" + +GSList *chatnets; /* list of available chat networks */ + +static void chatnet_config_save(CHATNET_REC *chatnet) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("chatnets", TRUE); + node = config_node_section(node, chatnet->name, NODE_TYPE_BLOCK); + iconfig_node_clear(node); + + iconfig_node_set_str(node, "type", chat_protocol_find_id(chatnet->chat_type)->name); + iconfig_node_set_str(node, "nick", chatnet->nick); + iconfig_node_set_str(node, "username", chatnet->username); + iconfig_node_set_str(node, "realname", chatnet->realname); + iconfig_node_set_str(node, "host", chatnet->own_host); + iconfig_node_set_str(node, "autosendcmd", chatnet->autosendcmd); + + signal_emit("chatnet saved", 2, chatnet, node); +} + +static void chatnet_config_remove(CHATNET_REC *chatnet) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("chatnets", FALSE); + if (node != NULL) iconfig_node_set_str(node, chatnet->name, NULL); +} + +void chatnet_create(CHATNET_REC *chatnet) +{ + g_return_if_fail(chatnet != NULL); + + chatnet->type = module_get_uniq_id("CHATNET", 0); + if (g_slist_find(chatnets, chatnet) == NULL) + chatnets = g_slist_append(chatnets, chatnet); + + chatnet_config_save(chatnet); + signal_emit("chatnet created", 1, chatnet); +} + +void chatnet_remove(CHATNET_REC *chatnet) +{ + g_return_if_fail(IS_CHATNET(chatnet)); + + signal_emit("chatnet removed", 1, chatnet); + + chatnet_config_remove(chatnet); + chatnet_destroy(chatnet); +} + +void chatnet_destroy(CHATNET_REC *chatnet) +{ + g_return_if_fail(IS_CHATNET(chatnet)); + + chatnets = g_slist_remove(chatnets, chatnet); + signal_emit("chatnet destroyed", 1, chatnet); + + g_free_not_null(chatnet->nick); + g_free_not_null(chatnet->username); + g_free_not_null(chatnet->realname); + g_free_not_null(chatnet->own_host); + g_free_not_null(chatnet->autosendcmd); + g_free(chatnet->name); + g_free(chatnet); +} + +/* Find the chat network by name */ +CHATNET_REC *chatnet_find(const char *name) +{ + GSList *tmp; + + g_return_val_if_fail(name != NULL, NULL); + + for (tmp = chatnets; tmp != NULL; tmp = tmp->next) { + CHATNET_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +static void sig_connected(SERVER_REC *server) +{ + CHATNET_REC *rec; + + g_return_if_fail(IS_SERVER(server)); + + if (server->connrec->chatnet == NULL) + return; + + rec = chatnet_find(server->connrec->chatnet); + if (rec != NULL && rec->autosendcmd) + eval_special_string(rec->autosendcmd, "", server, NULL); +} + +static void chatnet_read(CONFIG_NODE *node) +{ + CHAT_PROTOCOL_REC *proto; + CHATNET_REC *rec; + char *type; + + if (node == NULL || node->key == NULL) + return; + + type = config_node_get_str(node, "type", NULL); + proto = type == NULL ? NULL : chat_protocol_find(type); + if (proto == NULL) { + proto = type == NULL ? chat_protocol_get_default() : + chat_protocol_get_unknown(type); + } + + if (type == NULL) + iconfig_node_set_str(node, "type", proto->name); + + rec = proto->create_chatnet(); + rec->type = module_get_uniq_id("CHATNET", 0); + rec->chat_type = proto->id; + rec->name = g_strdup(node->key); + rec->nick = g_strdup(config_node_get_str(node, "nick", NULL)); + rec->username = g_strdup(config_node_get_str(node, "username", NULL)); + rec->realname = g_strdup(config_node_get_str(node, "realname", NULL)); + rec->own_host = g_strdup(config_node_get_str(node, "host", NULL)); + rec->autosendcmd = g_strdup(config_node_get_str(node, "autosendcmd", NULL)); + + chatnets = g_slist_append(chatnets, rec); + signal_emit("chatnet read", 2, rec, node); +} + +static void read_chatnets(void) +{ + CONFIG_NODE *node; + + while (chatnets != NULL) + chatnet_destroy(chatnets->data); + + node = iconfig_node_traverse("chatnets", FALSE); + if (node == NULL) { + /* FIXME: remove after .98 */ + node = iconfig_node_traverse("ircnets", FALSE); + if (node != NULL) { + /* very dirty method - doesn't update hashtables + but this will do temporarily.. */ + g_free(node->key); + node->key = g_strdup("chatnets"); + } + } + + if (node != NULL) + g_slist_foreach(node->value, (GFunc) chatnet_read, NULL); +} + +void chatnets_init(void) +{ + chatnets = NULL; + + signal_add("event connected", (SIGNAL_FUNC) sig_connected); + signal_add("setup reread", (SIGNAL_FUNC) read_chatnets); + signal_add_first("irssi init read settings", (SIGNAL_FUNC) read_chatnets); +} + +void chatnets_deinit(void) +{ + while (chatnets != NULL) + chatnet_destroy(chatnets->data); + module_uniq_destroy("CHATNET"); + + signal_remove("event connected", (SIGNAL_FUNC) sig_connected); + signal_remove("setup reread", (SIGNAL_FUNC) read_chatnets); + signal_remove("irssi init read settings", (SIGNAL_FUNC) read_chatnets); +} diff --git a/apps/irssi/src/core/chatnets.h b/apps/irssi/src/core/chatnets.h new file mode 100644 index 00000000..2b78f64a --- /dev/null +++ b/apps/irssi/src/core/chatnets.h @@ -0,0 +1,32 @@ +#ifndef __CHATNETS_H +#define __CHATNETS_H + +#include "modules.h" + +/* Returns CHATNET_REC if it's chatnet, NULL if it isn't. */ +#define CHATNET(chatnet) \ + MODULE_CHECK_CAST(chatnet, CHATNET_REC, type, "CHATNET") + +#define IS_CHATNET(chatnet) \ + (CHATNET(chatnet) ? TRUE : FALSE) + +struct _CHATNET_REC { +#include "chatnet-rec.h" +}; + +extern GSList *chatnets; /* list of available chat networks */ + +/* add the chatnet to chat networks list */ +void chatnet_create(CHATNET_REC *chatnet); +/* remove the chatnet from chat networks list */ +void chatnet_remove(CHATNET_REC *chatnet); +/* destroy the chatnet structure. doesn't remove from config file */ +void chatnet_destroy(CHATNET_REC *chatnet); + +/* Find the chat network by name */ +CHATNET_REC *chatnet_find(const char *name); + +void chatnets_init(void); +void chatnets_deinit(void); + +#endif diff --git a/apps/irssi/src/core/commands.c b/apps/irssi/src/core/commands.c new file mode 100644 index 00000000..c4c88908 --- /dev/null +++ b/apps/irssi/src/core/commands.c @@ -0,0 +1,911 @@ +/* + commands.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "special-vars.h" +#include "window-item-def.h" + +#include "servers.h" +#include "servers-redirect.h" +#include "channels.h" + +#include "lib-config/iconfig.h" +#include "settings.h" + +GSList *commands; +char *current_command; + +static int signal_default_command; + +static GSList *alias_runstack; + +COMMAND_REC *command_find(const char *cmd) +{ + GSList *tmp; + + g_return_val_if_fail(cmd != NULL, NULL); + + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (g_strcasecmp(rec->cmd, cmd) == 0) + return rec; + } + + return NULL; +} + +static COMMAND_MODULE_REC *command_module_find(COMMAND_REC *rec, + const char *module) +{ + GSList *tmp; + + g_return_val_if_fail(rec != NULL, NULL); + g_return_val_if_fail(module != NULL, NULL); + + for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) { + COMMAND_MODULE_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, module) == 0) + return rec; + } + + return NULL; +} + +static COMMAND_MODULE_REC *command_module_find_func(COMMAND_REC *rec, + SIGNAL_FUNC func) +{ + GSList *tmp; + + g_return_val_if_fail(rec != NULL, NULL); + g_return_val_if_fail(func != NULL, NULL); + + for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) { + COMMAND_MODULE_REC *rec = tmp->data; + + if (g_slist_find(rec->signals, func) != NULL) + return rec; + } + + return NULL; +} + +int command_have_sub(const char *command) +{ + GSList *tmp; + int len; + + g_return_val_if_fail(command != NULL, FALSE); + + /* find "command "s */ + len = strlen(command); + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (g_strncasecmp(rec->cmd, command, len) == 0 && + rec->cmd[len] == ' ') + return TRUE; + } + + return FALSE; +} + +static COMMAND_MODULE_REC *command_module_get(COMMAND_REC *rec, + const char *module) +{ + COMMAND_MODULE_REC *modrec; + + g_return_val_if_fail(rec != NULL, NULL); + + modrec = command_module_find(rec, module); + if (modrec == NULL) { + modrec = g_new0(COMMAND_MODULE_REC, 1); + modrec->name = g_strdup(module); + rec->modules = g_slist_append(rec->modules, modrec); + } + + return modrec; +} + +void command_bind_to(const char *module, int pos, const char *cmd, + const char *category, SIGNAL_FUNC func) +{ + COMMAND_REC *rec; + COMMAND_MODULE_REC *modrec; + char *str; + + g_return_if_fail(module != NULL); + g_return_if_fail(cmd != NULL); + + rec = command_find(cmd); + if (rec == NULL) { + rec = g_new0(COMMAND_REC, 1); + rec->cmd = g_strdup(cmd); + rec->category = category == NULL ? NULL : g_strdup(category); + commands = g_slist_append(commands, rec); + } + modrec = command_module_get(rec, module); + + modrec->signals = g_slist_append(modrec->signals, func); + + if (func != NULL) { + str = g_strconcat("command ", cmd, NULL); + signal_add_to(module, pos, str, func); + g_free(str); + } + + signal_emit("commandlist new", 1, rec); +} + +static void command_free(COMMAND_REC *rec) +{ + commands = g_slist_remove(commands, rec); + signal_emit("commandlist remove", 1, rec); + + g_free_not_null(rec->category); + g_strfreev(rec->options); + g_free(rec->cmd); + g_free(rec); +} + +static void command_module_free(COMMAND_MODULE_REC *modrec, COMMAND_REC *rec) +{ + rec->modules = g_slist_remove(rec->modules, modrec); + + g_slist_free(modrec->signals); + g_free(modrec->name); + g_free_not_null(modrec->options); + g_free(modrec); +} + +static void command_module_destroy(COMMAND_REC *rec, + COMMAND_MODULE_REC *modrec) +{ + GSList *tmp, *freelist; + + command_module_free(modrec, rec); + + /* command_set_options() might have added module declaration of it's + own without any signals .. check if they're the only ones left + and if so, destroy them. */ + freelist = NULL; + for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) { + COMMAND_MODULE_REC *rec = tmp->data; + + if (rec->signals == NULL) + freelist = g_slist_append(freelist, rec); + else { + g_slist_free(freelist); + freelist = NULL; + break; + } + } + + g_slist_foreach(freelist, (GFunc) command_module_free, rec); + g_slist_free(freelist); + + if (rec->modules == NULL) + command_free(rec); +} + +void command_unbind(const char *cmd, SIGNAL_FUNC func) +{ + COMMAND_REC *rec; + COMMAND_MODULE_REC *modrec; + char *str; + + g_return_if_fail(cmd != NULL); + g_return_if_fail(func != NULL); + + rec = command_find(cmd); + if (rec != NULL) { + modrec = command_module_find_func(rec, func); + modrec->signals = g_slist_remove(modrec->signals, func); + if (modrec->signals == NULL) + command_module_destroy(rec, modrec); + } + + str = g_strconcat("command ", cmd, NULL); + signal_remove(str, func); + g_free(str); +} + +/* Expand `cmd' - returns `cmd' if not found, NULL if more than one + match is found */ +static const char *command_expand(char *cmd) +{ + GSList *tmp; + const char *match; + int len, multiple; + + g_return_val_if_fail(cmd != NULL, NULL); + + multiple = FALSE; + match = NULL; + len = strlen(cmd); + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (g_strncasecmp(rec->cmd, cmd, len) == 0 && + strchr(rec->cmd+len, ' ') == NULL) { + if (rec->cmd[len] == '\0') { + /* full match */ + return rec->cmd; + } + + if (match != NULL) { + /* multiple matches, we still need to check + if there's some command left that is a + full match.. */ + multiple = TRUE; + } + + /* check that this is the only match */ + match = rec->cmd; + } + } + + if (multiple) { + signal_emit("error command", 2, + GINT_TO_POINTER(CMDERR_AMBIGUOUS), cmd); + return NULL; + } + + return match != NULL ? match : cmd; +} + +void command_runsub(const char *cmd, const char *data, + void *server, void *item) +{ + const char *newcmd; + char *orig, *subcmd, *defcmd, *args; + + g_return_if_fail(data != NULL); + + if (*data == '\0') { + /* no subcommand given - list the subcommands */ + signal_emit("list subcommands", 2, cmd); + return; + } + + /* get command.. */ + orig = subcmd = g_strdup_printf("command %s %s", cmd, data); + args = strchr(subcmd+8 + strlen(cmd)+1, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + while (*args == ' ') args++; + + /* check if this command can be expanded */ + newcmd = command_expand(subcmd+8); + if (newcmd == NULL) { + /* ambiguous command */ + g_free(orig); + return; + } + + subcmd = g_strconcat("command ", newcmd, NULL); + + g_strdown(subcmd); + if (!signal_emit(subcmd, 3, args, server, item)) { + defcmd = g_strdup_printf("default command %s", cmd); + if (!signal_emit(defcmd, 3, data, server, item)) { + signal_emit("error command", 2, + GINT_TO_POINTER(CMDERR_UNKNOWN), subcmd+8); + } + g_free(defcmd); + } + + g_free(subcmd); + g_free(orig); +} + +static GSList *optlist_find(GSList *optlist, const char *option) +{ + while (optlist != NULL) { + char *name = optlist->data; + if (iscmdtype(*name)) name++; + + if (g_strcasecmp(name, option) == 0) + return optlist; + + optlist = optlist->next; + } + + return NULL; +} + +int command_have_option(const char *cmd, const char *option) +{ + COMMAND_REC *rec; + char **tmp; + + g_return_val_if_fail(cmd != NULL, FALSE); + g_return_val_if_fail(option != NULL, FALSE); + + rec = command_find(cmd); + g_return_val_if_fail(rec != NULL, FALSE); + + if (rec->options == NULL) + return FALSE; + + for (tmp = rec->options; *tmp != NULL; tmp++) { + char *name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp; + + if (g_strcasecmp(name, option) == 0) + return TRUE; + } + + return FALSE; +} + +static void command_calc_options(COMMAND_REC *rec, const char *options) +{ + char **optlist, **tmp, *name, *str; + GSList *list, *oldopt; + + optlist = g_strsplit(options, " ", -1); + + if (rec->options == NULL) { + /* first call - use specified args directly */ + rec->options = optlist; + return; + } + + /* save old options to linked list */ + list = NULL; + for (tmp = rec->options; *tmp != NULL; tmp++) + list = g_slist_append(list, g_strdup(*tmp)); + g_strfreev(rec->options); + + /* merge the options */ + for (tmp = optlist; *tmp != NULL; tmp++) { + name = iscmdtype(**tmp) ? (*tmp)+1 : *tmp; + + oldopt = optlist_find(list, name); + if (oldopt != NULL) { + /* already specified - overwrite old defination */ + g_free(oldopt->data); + oldopt->data = g_strdup(*tmp); + } else { + /* new option, append to list */ + list = g_slist_append(list, g_strdup(*tmp)); + } + } + g_strfreev(optlist); + + /* linked list -> string[] */ + str = gslist_to_string(list, " "); + rec->options = g_strsplit(str, " ", -1); + g_free(str); + + g_slist_foreach(list, (GFunc) g_free, NULL); + g_slist_free(list); +} + +/* recalculate options to command from options in all modules */ +static void command_update_options(COMMAND_REC *rec) +{ + GSList *tmp; + + g_strfreev(rec->options); + rec->options = NULL; + + for (tmp = rec->modules; tmp != NULL; tmp = tmp->next) { + COMMAND_MODULE_REC *modrec = tmp->data; + + if (modrec->options != NULL) + command_calc_options(rec, modrec->options); + } +} + +void command_set_options_module(const char *module, + const char *cmd, const char *options) +{ + COMMAND_REC *rec; + COMMAND_MODULE_REC *modrec; + int reload; + + g_return_if_fail(module != NULL); + g_return_if_fail(cmd != NULL); + g_return_if_fail(options != NULL); + + rec = command_find(cmd); + g_return_if_fail(rec != NULL); + modrec = command_module_get(rec, module); + + reload = modrec->options != NULL; + if (reload) { + /* options already set for the module .. + we need to recalculate everything */ + g_free(modrec->options); + } + + modrec->options = g_strdup(options); + + if (reload) + command_update_options(rec); + else + command_calc_options(rec, options); +} + +char *cmd_get_param(char **data) +{ + char *pos; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(*data != NULL, NULL); + + while (**data == ' ') (*data)++; + pos = *data; + + while (**data != '\0' && **data != ' ') (*data)++; + if (**data == ' ') *(*data)++ = '\0'; + + return pos; +} + +static char *cmd_get_quoted_param(char **data) +{ + char *pos, quote; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(*data != NULL, NULL); + + while (**data == ' ') (*data)++; + if (**data != '\'' && **data != '"') + return cmd_get_param(data); + + quote = **data; (*data)++; + + pos = *data; + while (**data != '\0' && **data != quote) { + if (**data == '\\' && (*data)[1] != '\0') + g_memmove(*data, (*data)+1, strlen(*data)); + (*data)++; + } + + if (**data != '\0') *(*data)++ = '\0'; + + return pos; +} + +/* Find specified option from list of options - the `option' might be + shortened version of the full command. Returns index where the + option was found, -1 if not found or -2 if there was multiple matches. */ +static int option_find(char **array, const char *option) +{ + char **tmp; + int index, found, len, multiple; + + g_return_val_if_fail(array != NULL, -1); + g_return_val_if_fail(option != NULL, -1); + + len = strlen(option); + + found = -1; index = 0; multiple = FALSE; + for (tmp = array; *tmp != NULL; tmp++, index++) { + const char *text = *tmp + iscmdtype(**tmp); + + if (g_strncasecmp(text, option, len) == 0) { + if (text[len] == '\0') { + /* full match */ + return index; + } + + if (found != -1) { + /* multiple matches - we still need to check + if there's a full match left.. */ + multiple = TRUE; + } + + /* partial match, check that it's the only one */ + found = index; + } + } + + if (multiple) + return -2; + + return found; +} + +static int get_cmd_options(char **data, int ignore_unknown, + const char *cmd, GHashTable *options) +{ + COMMAND_REC *rec; + char *option, *arg, **optlist; + int pos; + + /* get option definations */ + rec = cmd == NULL ? NULL : command_find(cmd); + optlist = rec == NULL ? NULL : rec->options; + + option = NULL; pos = -1; + for (;;) { + if (**data == '-') { + if (option != NULL && *optlist[pos] == '+') { + /* required argument missing! */ + *data = optlist[pos] + 1; + return CMDERR_OPTION_ARG_MISSING; + } + + (*data)++; + if (**data == '-' && isspace((*data)[1])) { + /* -- option means end of options even + if next word starts with - */ + (*data)++; + while (isspace(**data)) (*data)++; + break; + } + + if (!isspace(**data)) + option = cmd_get_param(data); + else { + option = "-"; + (*data)++; + } + + /* check if this option can have argument */ + pos = optlist == NULL ? -1 : + option_find(optlist, option); + if (pos == -1 && !ignore_unknown) { + /* unknown option! */ + *data = option; + return CMDERR_OPTION_UNKNOWN; + } + if (pos == -2 && !ignore_unknown) { + /* multiple matches */ + *data = option; + return CMDERR_OPTION_AMBIGUOUS; + } + if (pos >= 0) { + /* if we used a shortcut of parameter, put + the whole parameter name in options table */ + option = optlist[pos] + + iscmdtype(*optlist[pos]); + } + if (options != NULL) + g_hash_table_insert(options, option, ""); + + if (pos < 0 || !iscmdtype(*optlist[pos]) || + *optlist[pos] == '!') + option = NULL; + + while (isspace(**data)) (*data)++; + continue; + } + + if (option == NULL) + break; + + if (*optlist[pos] == '@' && !isdigit(**data)) + break; /* expected a numeric argument */ + + /* save the argument */ + arg = cmd_get_quoted_param(data); + if (options != NULL) { + g_hash_table_remove(options, option); + g_hash_table_insert(options, option, arg); + } + option = NULL; + + while (isspace(**data)) (*data)++; + } + + return 0; +} + +typedef struct { + char *data; + GHashTable *options; +} CMD_TEMP_REC; + +static char *get_optional_channel(WI_ITEM_REC *active_item, char **data) +{ + CHANNEL_REC *chanrec; + char *tmp, *origtmp, *channel, *ret; + + if (active_item == NULL) { + /* no active channel in window, channel required */ + return cmd_get_param(data); + } + + origtmp = tmp = g_strdup(*data); + channel = cmd_get_param(&tmp); + + if (strcmp(channel, "*") == 0 || + !active_item->server->ischannel(channel)) + ret = active_item->name; + else { + /* Find the channel first and use it's name if found. + This allows automatic !channel -> !XXXXXchannel replaces. */ + channel = cmd_get_param(data); + + chanrec = channel_find(active_item->server, channel); + ret = chanrec == NULL ? channel : chanrec->name; + } + + g_free(origtmp); + return ret; +} + +int cmd_get_params(const char *data, gpointer *free_me, int count, ...) +{ + WI_ITEM_REC *item; + CMD_TEMP_REC *rec; + GHashTable **opthash; + char **str, *arg, *datad; + va_list args; + int cnt, error, ignore_unknown; + + g_return_val_if_fail(data != NULL, FALSE); + + va_start(args, count); + + rec = g_new0(CMD_TEMP_REC, 1); + rec->data = g_strdup(data); + *free_me = rec; + + datad = rec->data; + error = FALSE; + + item = (count & PARAM_FLAG_OPTCHAN) == 0 ? NULL: + (WI_ITEM_REC *) va_arg(args, WI_ITEM_REC *); + + if (count & PARAM_FLAG_OPTIONS) { + arg = (char *) va_arg(args, char *); + opthash = (GHashTable **) va_arg(args, GHashTable **); + + rec->options = *opthash = + g_hash_table_new((GHashFunc) g_istr_hash, + (GCompareFunc) g_istr_equal); + + ignore_unknown = count & PARAM_FLAG_UNKNOWN_OPTIONS; + error = get_cmd_options(&datad, ignore_unknown, + arg, *opthash); + } + + if (!error) { + /* and now handle the string */ + cnt = PARAM_WITHOUT_FLAGS(count); + if (count & PARAM_FLAG_OPTCHAN) { + /* optional channel as first parameter */ + arg = get_optional_channel(item, &datad); + + str = (char **) va_arg(args, char **); + if (str != NULL) *str = arg; + cnt--; + } + + while (cnt-- > 0) { + if (cnt == 0 && count & PARAM_FLAG_GETREST) { + /* get rest */ + arg = datad; + } else { + arg = (count & PARAM_FLAG_NOQUOTES) ? + cmd_get_param(&datad) : + cmd_get_quoted_param(&datad); + } + + str = (char **) va_arg(args, char **); + if (str != NULL) *str = arg; + } + } + va_end(args); + + if (error) { + signal_emit("error command", 2, GINT_TO_POINTER(error), datad); + signal_stop(); + + cmd_params_free(rec); + *free_me = NULL; + } + + return !error; +} + +void cmd_params_free(void *free_me) +{ + CMD_TEMP_REC *rec = free_me; + + if (rec->options != NULL) g_hash_table_destroy(rec->options); + g_free(rec->data); + g_free(rec); +} + +static void command_module_unbind_all(COMMAND_REC *rec, + COMMAND_MODULE_REC *modrec) +{ + GSList *tmp, *next; + + for (tmp = modrec->signals; tmp != NULL; tmp = next) { + next = tmp->next; + + command_unbind(rec->cmd, tmp->data); + } + + if (g_slist_find(commands, rec) != NULL) { + /* this module might have removed some options + from command, update them. */ + command_update_options(rec); + } +} + +void commands_remove_module(const char *module) +{ + GSList *tmp, *next, *modlist; + + g_return_if_fail(module != NULL); + + for (tmp = commands; tmp != NULL; tmp = next) { + COMMAND_REC *rec = tmp->data; + + next = tmp->next; + modlist = gslist_find_string(rec->modules, module); + if (modlist != NULL) + command_module_unbind_all(rec, modlist->data); + } +} + +#define alias_runstack_push(alias) \ + alias_runstack = g_slist_append(alias_runstack, alias) + +#define alias_runstack_pop(alias) \ + alias_runstack = g_slist_remove(alias_runstack, alias) + +#define alias_runstack_find(alias) \ + (gslist_find_icase_string(alias_runstack, alias) != NULL) + +static void parse_command(const char *command, int expand_aliases, + SERVER_REC *server, void *item) +{ + const char *alias, *newcmd; + char *cmd, *orig, *args, *oldcmd; + + g_return_if_fail(command != NULL); + + cmd = orig = g_strconcat("command ", command, NULL); + args = strchr(cmd+8, ' '); + if (args != NULL) *args++ = '\0'; else args = ""; + + /* check if there's an alias for command. Don't allow + recursive aliases */ + alias = !expand_aliases || alias_runstack_find(cmd+8) ? NULL : + alias_find(cmd+8); + if (alias != NULL) { + alias_runstack_push(cmd+8); + eval_special_string(alias, args, server, item); + alias_runstack_pop(cmd+8); + g_free(orig); + return; + } + + /* check if this command can be expanded */ + newcmd = command_expand(cmd+8); + if (newcmd == NULL) { + /* ambiguous command */ + g_free(orig); + return; + } + + cmd = g_strconcat("command ", newcmd, NULL); + if (server != NULL) + server_redirect_default(SERVER(server), cmd); + + g_strdown(cmd); + oldcmd = current_command; + current_command = cmd+8; + if (!signal_emit(cmd, 3, args, server, item)) { + signal_emit_id(signal_default_command, 3, + command, server, item); + } + current_command = oldcmd; + + g_free(cmd); + g_free(orig); +} + +static void event_command(const char *line, SERVER_REC *server, void *item) +{ + char *cmdchar; + int expand_aliases = TRUE; + + g_return_if_fail(line != NULL); + + if (*line == '\0') { + /* empty line, forget it. */ + signal_stop(); + return; + } + + cmdchar = strchr(settings_get_str("cmdchars"), *line); + if (cmdchar != NULL && line[1] == ' ') { + /* "/ text" = same as sending "text" to active channel. */ + line += 2; + cmdchar = NULL; + } + if (cmdchar == NULL) { + /* non-command - let someone else handle this */ + signal_emit("send text", 3, line, server, item); + return; + } + + /* same cmdchar twice ignores aliases ignores aliases */ + line++; + if (*line == *cmdchar) { + line++; + expand_aliases = FALSE; + } + + /* ^command hides the output - we'll do this at fe-common but + we have to skip the ^ char here.. */ + if (*line == '^') line++; + + parse_command(line, expand_aliases, server, item); +} + +/* SYNTAX: EVAL */ +static void cmd_eval(const char *data, SERVER_REC *server, void *item) +{ + g_return_if_fail(data != NULL); + + eval_special_string(data, "", server, item); +} + +/* SYNTAX: CD */ +static void cmd_cd(const char *data) +{ + char *str; + + g_return_if_fail(data != NULL); + if (*data == '\0') return; + + str = convert_home(data); + chdir(str); + g_free(str); +} + +void commands_init(void) +{ + commands = NULL; + current_command = NULL; + alias_runstack = NULL; + + signal_default_command = signal_get_uniq_id("default command"); + + settings_add_str("misc", "cmdchars", "/"); + signal_add("send command", (SIGNAL_FUNC) event_command); + + command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval); + command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd); +} + +void commands_deinit(void) +{ + g_free_not_null(current_command); + + signal_remove("send command", (SIGNAL_FUNC) event_command); + + command_unbind("eval", (SIGNAL_FUNC) cmd_eval); + command_unbind("cd", (SIGNAL_FUNC) cmd_cd); +} diff --git a/apps/irssi/src/core/commands.h b/apps/irssi/src/core/commands.h new file mode 100644 index 00000000..9dee2e80 --- /dev/null +++ b/apps/irssi/src/core/commands.h @@ -0,0 +1,147 @@ +#ifndef __COMMANDS_H +#define __COMMANDS_H + +#include "signals.h" + +typedef struct { + char *name; + char *options; + GSList *signals; +} COMMAND_MODULE_REC; + +typedef struct { + GSList *modules; + char *category; + char *cmd; + char **options; /* combined from modules[..]->options */ +} COMMAND_REC; + +enum { + CMDERR_OPTION_UNKNOWN = -3, /* unknown -option */ + CMDERR_OPTION_AMBIGUOUS = -2, /* ambiguous -option */ + CMDERR_OPTION_ARG_MISSING = -1, /* argument missing for -option */ + + CMDERR_UNKNOWN, /* unknown command */ + CMDERR_AMBIGUOUS, /* ambiguous command */ + + CMDERR_ERRNO, /* get the error from errno */ + CMDERR_NOT_ENOUGH_PARAMS, /* not enough parameters given */ + CMDERR_NOT_CONNECTED, /* not connected to IRC server */ + CMDERR_NOT_JOINED, /* not joined to any channels in this window */ + CMDERR_CHAN_NOT_FOUND, /* channel not found */ + CMDERR_CHAN_NOT_SYNCED, /* channel not fully synchronized yet */ + CMDERR_NOT_GOOD_IDEA /* not good idea to do, -yes overrides this */ +}; + +/* Return the full command for `alias' */ +#define alias_find(alias) \ + iconfig_get_str("aliases", alias, NULL) + +/* Returning from command function with error */ +#define cmd_return_error(a) \ + G_STMT_START { \ + signal_emit("error command", 1, GINT_TO_POINTER(a)); \ + signal_stop(); \ + return; \ + } G_STMT_END + +#define cmd_param_error(a) \ + G_STMT_START { \ + cmd_params_free(free_arg); \ + cmd_return_error(a); \ + } G_STMT_END + +extern GSList *commands; +extern char *current_command; /* the command we're right now. */ + +/* Bind command to specified function. */ +void command_bind_to(const char *module, int pos, const char *cmd, + const char *category, SIGNAL_FUNC func); +#define command_bind(a, b, c) command_bind_to(MODULE_NAME, 1, a, b, c) +#define command_bind_first(a, b, c) command_bind_to(MODULE_NAME, 0, a, b, c) +#define command_bind_last(a, b, c) command_bind_to(MODULE_NAME, 2, a, b, c) + +void command_unbind(const char *cmd, SIGNAL_FUNC func); + +/* Run subcommand, `cmd' contains the base command, first word in `data' + contains the subcommand */ +void command_runsub(const char *cmd, const char *data, + void *server, void *item); + +COMMAND_REC *command_find(const char *cmd); +int command_have_sub(const char *command); + +/* Specify options that command can accept. `options' contains list of + options separated with space, each option can contain a special + char in front of it: + + '!': no argument (default) + '-': optional argument + '+': argument required + '@': optional numeric argument + + for example if options = "save -file +nick", you can use + /command -save -file [] -nick + + You can call this command multiple times for same command, options + will be merged. If there's any conflicts with option types, the last + call will override the previous */ +#define iscmdtype(c) \ + ((c) == '!' || (c) == '-' || (c) == '+' || (c) == '@') +void command_set_options_module(const char *module, + const char *cmd, const char *options); +#define command_set_options(cmd, options) \ + command_set_options_module(MODULE_NAME, cmd, options) + +/* Returns TRUE if command has specified option. */ +int command_have_option(const char *cmd, const char *option); + +/* count can have these flags: */ +#define PARAM_WITHOUT_FLAGS(a) ((a) & 0x00000fff) +/* don't check for quotes - "arg1 arg2" is NOT treated as one argument */ +#define PARAM_FLAG_NOQUOTES 0x00001000 +/* final argument gets all the rest of the arguments */ +#define PARAM_FLAG_GETREST 0x00002000 +/* command contains options - first you need to specify them with + command_set_options() function. Example: + + -cmd requiredarg -noargcmd -cmd2 "another arg" -optnumarg rest of text + + You would call this with: + + // only once in init + command_set_options("mycmd", "+cmd noargcmd -cmd2 @optnumarg"); + + GHashTable *optlist; + + cmd_get_params(data, &free_me, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST, "mycmd", &optlist, &rest); + + The optlist hash table is filled: + + "cmd" = "requiredarg" + "noargcmd" = "" + "cmd2" = "another arg" + "optnumarg" = "" - this is because "rest" isn't a numeric value +*/ +#define PARAM_FLAG_OPTIONS 0x00004000 +/* don't complain about unknown options */ +#define PARAM_FLAG_UNKNOWN_OPTIONS 0x00008000 +/* optional channel in first argument */ +#define PARAM_FLAG_OPTCHAN 0x00010000 + +char *cmd_get_param(char **data); +/* get parameters from command - you should point free_me somewhere and + cmd_params_free() it after you don't use any of the parameters anymore. + + Returns TRUE if all ok, FALSE if error occured. */ +int cmd_get_params(const char *data, gpointer *free_me, int count, ...); + +void cmd_params_free(void *free_me); + +void commands_remove_module(const char *module); + +void commands_init(void); +void commands_deinit(void); + +#endif diff --git a/apps/irssi/src/core/core.c b/apps/irssi/src/core/core.c new file mode 100644 index 00000000..fd7459db --- /dev/null +++ b/apps/irssi/src/core/core.c @@ -0,0 +1,109 @@ +/* + core.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "pidwait.h" + +#include "net-disconnect.h" +#include "net-sendbuffer.h" +#include "signals.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "servers.h" +#include "chatnets.h" +#include "commands.h" +#include "expandos.h" +#include "write-buffer.h" +#include "log.h" +#include "rawlog.h" +#include "ignore.h" + +#include "channels.h" +#include "queries.h" +#include "nicklist.h" +#include "nickmatch-cache.h" + +void chat_commands_init(void); +void chat_commands_deinit(void); + +int irssi_gui; + +void core_init(void) +{ + modules_init(); +#ifndef WIN32 + pidwait_init(); +#endif + + net_disconnect_init(); + net_sendbuffer_init(); + signals_init(); + settings_init(); + commands_init(); + nickmatch_cache_init(); + + chat_protocols_init(); + chatnets_init(); + expandos_init(); + ignore_init(); + servers_init(); + write_buffer_init(); + log_init(); + rawlog_init(); + + channels_init(); + queries_init(); + nicklist_init(); + + chat_commands_init(); + settings_check(); +} + +void core_deinit(void) +{ + chat_commands_deinit(); + + nicklist_deinit(); + queries_deinit(); + channels_deinit(); + + rawlog_deinit(); + log_deinit(); + write_buffer_deinit(); + servers_deinit(); + ignore_deinit(); + expandos_deinit(); + chatnets_deinit(); + chat_protocols_deinit(); + + nickmatch_cache_deinit(); + commands_deinit(); + settings_deinit(); + signals_deinit(); + net_sendbuffer_deinit(); + net_disconnect_deinit(); + +#ifndef WIN32 + pidwait_deinit(); +#endif + modules_deinit(); +} diff --git a/apps/irssi/src/core/core.h b/apps/irssi/src/core/core.h new file mode 100644 index 00000000..61d6ef70 --- /dev/null +++ b/apps/irssi/src/core/core.h @@ -0,0 +1,17 @@ +#ifndef __IRSSI_CORE_H +#define __IRSSI_CORE_H + +/* for determining what GUI is currently in use: */ +#define IRSSI_GUI_NONE 0 +#define IRSSI_GUI_TEXT 1 +#define IRSSI_GUI_GTK 2 +#define IRSSI_GUI_GNOME 3 +#define IRSSI_GUI_QT 4 +#define IRSSI_GUI_KDE 5 + +extern int irssi_gui; + +void core_init(void); +void core_deinit(void); + +#endif diff --git a/apps/irssi/src/core/expandos.c b/apps/irssi/src/core/expandos.c new file mode 100644 index 00000000..21ebae6b --- /dev/null +++ b/apps/irssi/src/core/expandos.c @@ -0,0 +1,585 @@ +/* + expandos.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "signals.h" +#include "expandos.h" +#include "settings.h" +#include "commands.h" +#include "misc.h" +#include "irssi-version.h" + +#include "servers.h" +#include "channels.h" +#include "queries.h" +#include "window-item-def.h" + +#ifdef HAVE_SYS_UTSNAME_H +# include +#endif + +#define MAX_EXPANDO_SIGNALS 10 + +typedef struct { + EXPANDO_FUNC func; + + int signals; + int signal_ids[MAX_EXPANDO_SIGNALS]; + int signal_args[MAX_EXPANDO_SIGNALS]; +} EXPANDO_REC; + +static int timer_tag; + +static EXPANDO_REC *char_expandos[127]; +static GHashTable *expandos; +static time_t client_start_time; +static char *last_sent_msg, *last_sent_msg_body; +static char *last_privmsg_from, *last_public_from; +static char *sysname, *sysrelease, *sysarch; +static const char *timestamp_format; + +#define CHAR_EXPANDOS_COUNT \ + ((int) (sizeof(char_expandos) / sizeof(char_expandos[0]))) + +/* Create expando - overrides any existing ones. */ +void expando_create(const char *key, EXPANDO_FUNC func, ...) +{ + EXPANDO_REC *rec; + const char *signal; + va_list va; + + g_return_if_fail(key != NULL || *key == '\0'); + g_return_if_fail(func != NULL); + + if (key[1] != '\0') + rec = g_hash_table_lookup(expandos, key); + else { + /* single character expando */ + rec = char_expandos[(int) *key]; + } + + if (rec != NULL) + rec->signals = 0; + else { + rec = g_new0(EXPANDO_REC, 1); + if (key[1] != '\0') + g_hash_table_insert(expandos, g_strdup(key), rec); + else + char_expandos[(int) *key] = rec; + } + + rec->func = func; + + va_start(va, func); + while ((signal = (const char *) va_arg(va, const char *)) != NULL) + expando_add_signal(key, signal, (int) va_arg(va, int)); + va_end(va); +} + +static EXPANDO_REC *expando_find(const char *key) +{ + if (key[1] != '\0') + return g_hash_table_lookup(expandos, key); + else + return char_expandos[(int) *key]; +} + +/* Add new signal to expando */ +void expando_add_signal(const char *key, const char *signal, ExpandoArg arg) +{ + EXPANDO_REC *rec; + + g_return_if_fail(key != NULL); + g_return_if_fail(signal != NULL); + + rec = expando_find(key); + g_return_if_fail(rec != NULL); + + if (arg == EXPANDO_NEVER) { + /* expando changes never */ + rec->signals = -1; + } else if (rec->signals < MAX_EXPANDO_SIGNALS) { + g_return_if_fail(rec->signals != -1); + + rec->signal_ids[rec->signals] = signal_get_uniq_id(signal); + rec->signal_args[rec->signals] = arg; + rec->signals++; + } +} + +/* Destroy expando */ +void expando_destroy(const char *key, EXPANDO_FUNC func) +{ + gpointer origkey; + EXPANDO_REC *rec; + + g_return_if_fail(key != NULL || *key == '\0'); + g_return_if_fail(func != NULL); + + if (key[1] == '\0') { + /* single character expando */ + rec = char_expandos[(int) *key]; + if (rec != NULL && rec->func == func) { + char_expandos[(int) *key] = NULL; + g_free(rec); + } + } else if (g_hash_table_lookup_extended(expandos, key, &origkey, + (gpointer *) &rec)) { + if (rec->func == func) { + g_hash_table_remove(expandos, key); + g_free(origkey); + g_free(rec); + } + } +} + +void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs) +{ + SIGNAL_FUNC func; + EXPANDO_REC *rec; + int n, arg; + + g_return_if_fail(key != NULL); + g_return_if_fail(funccount >= 1); + g_return_if_fail(funcs != NULL); + g_return_if_fail(funcs[0] != NULL); + + rec = expando_find(key); + g_return_if_fail(rec != NULL); + + if (rec->signals == 0) { + /* it's unknown when this expando changes.. + check it once in a second */ + signal_add("expando timer", funcs[EXPANDO_ARG_NONE]); + } + + for (n = 0; n < rec->signals; n++) { + arg = rec->signal_args[n]; + func = arg < funccount ? funcs[arg] : NULL; + if (func == NULL) func = funcs[EXPANDO_ARG_NONE]; + + signal_add_to_id(MODULE_NAME, 1, rec->signal_ids[n], func); + } +} + +void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs) +{ + SIGNAL_FUNC func; + EXPANDO_REC *rec; + int n, arg; + + g_return_if_fail(key != NULL); + g_return_if_fail(funccount >= 1); + g_return_if_fail(funcs != NULL); + g_return_if_fail(funcs[0] != NULL); + + rec = expando_find(key); + g_return_if_fail(rec != NULL); + + if (rec->signals == 0) { + /* it's unknown when this expando changes.. + check it once in a second */ + signal_remove("expando timer", funcs[EXPANDO_ARG_NONE]); + } + + for (n = 0; n < rec->signals; n++) { + arg = rec->signal_args[n]; + func = arg < funccount ? funcs[arg] : NULL; + if (func == NULL) func = funcs[EXPANDO_ARG_NONE]; + + signal_remove_id(rec->signal_ids[n], func); + } +} + +EXPANDO_FUNC expando_find_char(char chr) +{ + g_return_val_if_fail(chr < CHAR_EXPANDOS_COUNT, NULL); + + return char_expandos[(int) chr] == NULL ? NULL : + char_expandos[(int) chr]->func; +} + +EXPANDO_FUNC expando_find_long(const char *key) +{ + EXPANDO_REC *rec = g_hash_table_lookup(expandos, key); + return rec == NULL ? NULL : rec->func; +} + +/* last person who sent you a MSG */ +static char *expando_lastmsg(SERVER_REC *server, void *item, int *free_ret) +{ + return last_privmsg_from; +} + +/* last person to whom you sent a MSG */ +static char *expando_lastmymsg(SERVER_REC *server, void *item, int *free_ret) +{ + return last_sent_msg; +} + +/* last person to send a public message to a channel you are on */ +static char *expando_lastpublic(SERVER_REC *server, void *item, int *free_ret) +{ + return last_public_from; +} + +/* text of your AWAY message, if any */ +static char *expando_awaymsg(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL ? "" : server->away_reason; +} + +/* body of last MSG you sent */ +static char *expando_lastmymsg_body(SERVER_REC *server, void *item, int *free_ret) +{ + return last_sent_msg_body; +} + +/* current channel */ +static char *expando_channel(SERVER_REC *server, void *item, int *free_ret) +{ + return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->name; +} + +/* time client was started, $time() format */ +static char *expando_clientstarted(SERVER_REC *server, void *item, int *free_ret) +{ + *free_ret = TRUE; + return g_strdup_printf("%ld", (long) client_start_time); +} + +/* channel you were last INVITEd to */ +static char *expando_last_invite(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL ? "" : server->last_invite; +} + +/* client version text string */ +static char *expando_version(SERVER_REC *server, void *item, int *free_ret) +{ + return IRSSI_VERSION; +} + +/* current value of CMDCHARS */ +static char *expando_cmdchars(SERVER_REC *server, void *item, int *free_ret) +{ + return (char *) settings_get_str("cmdchars"); +} + +/* modes of current channel, if any */ +static char *expando_chanmode(SERVER_REC *server, void *item, int *free_ret) +{ + return !IS_CHANNEL(item) ? NULL : CHANNEL(item)->mode; +} + +/* current nickname */ +static char *expando_nick(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL ? "" : server->nick; +} + +/* value of STATUS_OPER if you are an irc operator */ +static char *expando_statusoper(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL || !server->server_operator ? "" : + (char *) settings_get_str("STATUS_OPER"); +} + +/* if you are a channel operator in $C, expands to a '@' */ +static char *expando_chanop(SERVER_REC *server, void *item, int *free_ret) +{ + return IS_CHANNEL(item) && CHANNEL(item)->chanop ? "@" : ""; +} + +/* nickname of whomever you are QUERYing */ +static char *expando_query(SERVER_REC *server, void *item, int *free_ret) +{ + return !IS_QUERY(item) ? "" : QUERY(item)->name; +} + +/* version of current server */ +static char *expando_serverversion(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL ? "" : server->version; +} + +/* target of current input (channel or QUERY nickname) */ +static char *expando_target(SERVER_REC *server, void *item, int *free_ret) +{ + return item == NULL ? "" : ((WI_ITEM_REC *) item)->name; +} + +/* client release date (numeric version string) */ +static char *expando_releasedate(SERVER_REC *server, void *item, int *free_ret) +{ + return IRSSI_VERSION_DATE; +} + +/* current working directory */ +static char *expando_workdir(SERVER_REC *server, void *item, int *free_ret) +{ + *free_ret = TRUE; + return g_get_current_dir(); +} + +/* value of REALNAME */ +static char *expando_realname(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL ? "" : server->connrec->realname; +} + +/* time of day (hh:mm) */ +static char *expando_time(SERVER_REC *server, void *item, int *free_ret) +{ + time_t now; + struct tm *tm; + char str[256]; + + now = time(NULL); + tm = localtime(&now); + + if (strftime(str, sizeof(str), timestamp_format, tm) == 0) + return ""; + + *free_ret = TRUE; + return g_strdup(str); +} + +/* a literal '$' */ +static char *expando_dollar(SERVER_REC *server, void *item, int *free_ret) +{ + return "$"; +} + +/* system name */ +static char *expando_sysname(SERVER_REC *server, void *item, int *free_ret) +{ + return sysname; +} + +/* system release */ +static char *expando_sysrelease(SERVER_REC *server, void *item, int *free_ret) +{ + return sysrelease; +} + +/* system architecture */ +static char *expando_sysarch(SERVER_REC *server, void *item, int *free_ret) +{ + return sysarch; +} + +/* Topic of active channel (or address of queried nick) */ +static char *expando_topic(SERVER_REC *server, void *item, int *free_ret) +{ + return IS_CHANNEL(item) ? CHANNEL(item)->topic : + IS_QUERY(item) ? QUERY(item)->address : ""; +} + +/* Server tag */ +static char *expando_servertag(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL ? "" : server->tag; +} + +/* Server chatnet */ +static char *expando_chatnet(SERVER_REC *server, void *item, int *free_ret) +{ + return server == NULL ? "" : server->connrec->chatnet; +} + +static void sig_message_public(SERVER_REC *server, const char *msg, + const char *nick, const char *address, + const char *target) +{ + g_free_not_null(last_public_from); + last_public_from = g_strdup(nick); +} + +static void sig_message_private(SERVER_REC *server, const char *msg, + const char *nick, const char *address) +{ + g_free_not_null(last_privmsg_from); + last_privmsg_from = g_strdup(nick); +} + +static void sig_message_own_private(SERVER_REC *server, const char *msg, + const char *target, const char *origtarget) +{ + g_return_if_fail(server != NULL); + g_return_if_fail(msg != NULL); + + if (target != NULL) { + if (target != last_sent_msg) { + g_free_not_null(last_sent_msg); + last_sent_msg = g_strdup(target); + } + g_free_not_null(last_sent_msg_body); + last_sent_msg_body = g_strdup(msg); + } +} + +static int sig_timer(void) +{ + signal_emit("expando timer", 0); + return 1; +} + +static void read_settings(void) +{ + timestamp_format = settings_get_str("timestamp_format"); +} + +void expandos_init(void) +{ +#ifdef HAVE_SYS_UTSNAME_H + struct utsname un; +#endif + settings_add_str("misc", "STATUS_OPER", "*"); + settings_add_str("misc", "timestamp_format", "%H:%M"); + + client_start_time = time(NULL); + last_sent_msg = NULL; last_sent_msg_body = NULL; + last_privmsg_from = NULL; last_public_from = NULL; + + sysname = sysrelease = sysarch = NULL; +#ifdef HAVE_SYS_UTSNAME_H + if (uname(&un) == 0) { + sysname = g_strdup(un.sysname); + sysrelease = g_strdup(un.release); + sysarch = g_strdup(un.machine); + } +#endif + + memset(char_expandos, 0, sizeof(char_expandos)); + expandos = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + + expando_create(",", expando_lastmsg, + "message private", EXPANDO_ARG_SERVER, NULL); + expando_create(".", expando_lastmymsg, + "command msg", EXPANDO_ARG_NONE, NULL); + expando_create(";", expando_lastpublic, + "message public", EXPANDO_ARG_SERVER, NULL); + expando_create("A", expando_awaymsg, + "away mode changed", EXPANDO_ARG_NONE, NULL); + expando_create("B", expando_lastmymsg_body, + "command msg", EXPANDO_ARG_NONE, NULL); + expando_create("C", expando_channel, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("F", expando_clientstarted, + "", EXPANDO_NEVER, NULL); + expando_create("I", expando_last_invite, NULL); + expando_create("J", expando_version, + "", EXPANDO_NEVER, NULL); + expando_create("K", expando_cmdchars, + "setup changed", EXPANDO_ARG_NONE, NULL); + expando_create("M", expando_chanmode, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, + "channel mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL); + expando_create("N", expando_nick, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, + "server nick changed", EXPANDO_ARG_SERVER, NULL); + expando_create("O", expando_statusoper, + "setup changed", EXPANDO_ARG_NONE, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, + "user mode changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("P", expando_chanop, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, + "nick mode changed", EXPANDO_ARG_WINDOW_ITEM, NULL); + expando_create("Q", expando_query, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("R", expando_serverversion, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("T", expando_target, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("V", expando_releasedate, + "", EXPANDO_NEVER, NULL); + expando_create("W", expando_workdir, NULL); + expando_create("Y", expando_realname, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("Z", expando_time, NULL); + expando_create("$", expando_dollar, + "", EXPANDO_NEVER, NULL); + + expando_create("sysname", expando_sysname, + "", EXPANDO_NEVER, NULL); + expando_create("sysrelease", expando_sysrelease, + "", EXPANDO_NEVER, NULL); + expando_create("sysarch", expando_sysarch, + "", EXPANDO_NEVER, NULL); + expando_create("topic", expando_topic, + "window changed", EXPANDO_ARG_NONE, + "window item changed", EXPANDO_ARG_WINDOW, + "channel topic changed", EXPANDO_ARG_WINDOW_ITEM, + "query address changed", EXPANDO_ARG_WINDOW_ITEM, NULL); + expando_create("tag", expando_servertag, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("chatnet", expando_chatnet, + "window changed", EXPANDO_ARG_NONE, + "window server changed", EXPANDO_ARG_WINDOW, NULL); + + read_settings(); + + timer_tag = g_timeout_add(1000, (GSourceFunc) sig_timer, NULL); + signal_add("message public", (SIGNAL_FUNC) sig_message_public); + signal_add("message private", (SIGNAL_FUNC) sig_message_private); + signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_add_first("setup changed", (SIGNAL_FUNC) read_settings); +} + +void expandos_deinit(void) +{ + int n; + + for (n = 0; n < CHAR_EXPANDOS_COUNT; n++) + g_free_not_null(char_expandos[n]); + + expando_destroy("sysname", expando_sysname); + expando_destroy("sysrelease", expando_sysrelease); + expando_destroy("sysarch", expando_sysarch); + expando_destroy("topic", expando_topic); + expando_destroy("tag", expando_servertag); + expando_destroy("chatnet", expando_chatnet); + + g_hash_table_destroy(expandos); + + g_free_not_null(last_sent_msg); g_free_not_null(last_sent_msg_body); + g_free_not_null(last_privmsg_from); g_free_not_null(last_public_from); + g_free_not_null(sysname); g_free_not_null(sysrelease); + g_free_not_null(sysarch); + + g_source_remove(timer_tag); + signal_remove("message public", (SIGNAL_FUNC) sig_message_public); + signal_remove("message private", (SIGNAL_FUNC) sig_message_private); + signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/core/expandos.h b/apps/irssi/src/core/expandos.h new file mode 100644 index 00000000..3dcb527e --- /dev/null +++ b/apps/irssi/src/core/expandos.h @@ -0,0 +1,38 @@ +#ifndef __EXPANDOS_H +#define __EXPANDOS_H + +#include "signals.h" + +/* first argument of signal must match to active .. */ +typedef enum { + EXPANDO_ARG_NONE, + EXPANDO_ARG_SERVER, + EXPANDO_ARG_WINDOW, + EXPANDO_ARG_WINDOW_ITEM, + + EXPANDO_NEVER /* special: expando never changes */ +} ExpandoArg; + +typedef char* (*EXPANDO_FUNC) + (SERVER_REC *server, void *item, int *free_ret); + +/* Create expando - overrides any existing ones. + ... = signal, type, ..., NULL - list of signals that might change the + value of this expando */ +void expando_create(const char *key, EXPANDO_FUNC func, ...); +/* Add new signal to expando */ +void expando_add_signal(const char *key, const char *signal, ExpandoArg arg); +/* Destroy expando */ +void expando_destroy(const char *key, EXPANDO_FUNC func); + +void expando_bind(const char *key, int funccount, SIGNAL_FUNC *funcs); +void expando_unbind(const char *key, int funccount, SIGNAL_FUNC *funcs); + +/* internal: */ +EXPANDO_FUNC expando_find_char(char chr); +EXPANDO_FUNC expando_find_long(const char *key); + +void expandos_init(void); +void expandos_deinit(void); + +#endif diff --git a/apps/irssi/src/core/ignore.c b/apps/irssi/src/core/ignore.c new file mode 100644 index 00000000..69460c5b --- /dev/null +++ b/apps/irssi/src/core/ignore.c @@ -0,0 +1,484 @@ +/* + ignore.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "levels.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "masks.h" +#include "servers.h" +#include "channels.h" +#include "nicklist.h" +#include "nickmatch-cache.h" + +#include "ignore.h" + +GSList *ignores; + +static NICKMATCH_REC *nickmatch; +static int time_tag; + +/* check if `text' contains ignored nick at the start of the line. */ +static int ignore_check_replies_rec(IGNORE_REC *rec, CHANNEL_REC *channel, + const char *text) +{ + GSList *nicks, *tmp; + + nicks = nicklist_find_multiple(channel, rec->mask); + if (nicks == NULL) return FALSE; + + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *nick = tmp->data; + + if (nick_match_msg(channel, text, nick->nick)) + return TRUE; + } + g_slist_free(nicks); + + return FALSE; +} + +static int ignore_check_replies(CHANNEL_REC *chanrec, const char *text) +{ + GSList *tmp; + + if (text == NULL || chanrec == NULL) + return FALSE; + + /* check reply ignores */ + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (rec->mask != NULL && rec->replies && + ignore_check_replies_rec(rec, chanrec, text)) + return TRUE; + } + + return FALSE; +} + +static int ignore_match_pattern(IGNORE_REC *rec, const char *text) +{ + if (rec->pattern == NULL) + return TRUE; + + if (text == NULL) + return FALSE; + + if (rec->regexp) { +#ifdef HAVE_REGEX_H + return rec->regexp_compiled && + regexec(&rec->preg, text, 0, NULL, 0) == 0; +#else + return FALSE; +#endif + } + + return rec->fullword ? + stristr_full(text, rec->pattern) != NULL : + stristr(text, rec->pattern) != NULL; +} + +#define ignore_match_level(rec, level) \ + ((level & (rec)->level) != 0) + +#define ignore_match_nickmask(rec, nick, nickmask) \ + ((rec)->mask == NULL || \ + (strchr((rec)->mask, '!') != NULL ? \ + match_wildcards((rec)->mask, nickmask) : \ + match_wildcards((rec)->mask, nick))) + +#define ignore_match_server(rec, server) \ + ((rec)->servertag == NULL || \ + g_strcasecmp((server)->tag, (rec)->servertag) == 0) + +#define ignore_match_channel(rec, channel) \ + ((rec)->channels == NULL || ((channel) != NULL && \ + strarray_find((rec)->channels, (channel)) != -1)) + +static int ignore_check_without_mask(GSList *list, CHANNEL_REC *channel, + int level, const char *text) +{ + GSList *tmp; + int len, best_mask, best_match, best_patt; + + best_mask = best_patt = -1; best_match = FALSE; + for (tmp = list; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (ignore_match_level(rec, level) && + ignore_match_pattern(rec, text)) { + len = rec->mask == NULL ? 0 : strlen(rec->mask); + if (len > best_mask) { + best_mask = len; + best_match = !rec->exception; + } else if (len == best_mask && rec->pattern != NULL) { + len = strlen(rec->pattern); + if (len > best_patt) { + best_patt = len; + best_match = !rec->exception; + } + } + } + } + + if (best_match || (level & MSGLEVEL_PUBLIC) == 0) + return best_match; + + return ignore_check_replies(channel, text); +} + +int ignore_check(SERVER_REC *server, const char *nick, const char *host, + const char *channel, const char *text, int level) +{ + CHANNEL_REC *chanrec; + NICK_REC *nickrec; + IGNORE_REC *rec; + GSList *tmp, *list; + char *nickmask; + int len, best_mask, best_match, best_patt; + + g_return_val_if_fail(server != NULL, 0); + if (nick == NULL) nick = ""; + + chanrec = (channel != NULL && server != NULL && + server->ischannel(channel)) ? + channel_find(server, channel) : NULL; + if (chanrec != NULL && nick != NULL && + (nickrec = nicklist_find(chanrec, nick)) != NULL) { + /* nick found - check only ignores in nickmatch cache */ + if (nickrec->host == NULL) + nicklist_set_host(chanrec, nickrec, host); + + list = nickmatch_find(nickmatch, nickrec); + return ignore_check_without_mask(list, chanrec, level, text); + } + + nickmask = g_strconcat(nick, "!", host, NULL); + + best_mask = best_patt = -1; best_match = FALSE; + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (ignore_match_level(rec, level) && + ignore_match_server(rec, server) && + ignore_match_channel(rec, channel) && + ignore_match_nickmask(rec, nick, nickmask) && + ignore_match_pattern(rec, text)) { + len = rec->mask == NULL ? 0 : strlen(rec->mask); + if (len > best_mask) { + best_mask = len; + best_match = !rec->exception; + } else if (len == best_mask && rec->pattern != NULL) { + len = strlen(rec->pattern); + if (len > best_patt) { + best_patt = len; + best_match = !rec->exception; + } + } + } + } + g_free(nickmask); + + if (best_match || (level & MSGLEVEL_PUBLIC) == 0) + return best_match; + + return ignore_check_replies(chanrec, text); +} + +IGNORE_REC *ignore_find(const char *servertag, const char *mask, + char **channels) +{ + GSList *tmp; + char **chan; + int ignore_servertag; + + if (mask != NULL && (*mask == '\0' || strcmp(mask, "*") == 0)) + mask = NULL; + + ignore_servertag = servertag != NULL && strcmp(servertag, "*") == 0; + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (!ignore_servertag) { + if ((servertag == NULL && rec->servertag != NULL) || + (servertag != NULL && rec->servertag == NULL)) + continue; + + if (servertag != NULL && g_strcasecmp(servertag, rec->servertag) != 0) + continue; + } + + if ((rec->mask == NULL && mask != NULL) || + (rec->mask != NULL && mask == NULL)) continue; + + if (rec->mask != NULL && g_strcasecmp(rec->mask, mask) != 0) + continue; + + if ((channels == NULL && rec->channels == NULL)) + return rec; /* no channels - ok */ + + if (channels != NULL && strcmp(*channels, "*") == 0) + return rec; /* ignore channels */ + + if (channels == NULL || rec->channels == NULL) + continue; /* other doesn't have channels */ + + if (strarray_length(channels) != strarray_length(rec->channels)) + continue; /* different amount of channels */ + + /* check that channels match */ + for (chan = channels; *chan != NULL; chan++) { + if (strarray_find(rec->channels, *chan) == -1) + break; + } + + if (*chan == NULL) + return rec; /* channels ok */ + } + + return NULL; +} + +static void ignore_set_config(IGNORE_REC *rec) +{ + CONFIG_NODE *node; + char *levelstr; + + if (rec->level == 0 || rec->unignore_time > 0) + return; + + node = iconfig_node_traverse("(ignores", TRUE); + node = config_node_section(node, NULL, NODE_TYPE_BLOCK); + + if (rec->mask != NULL) iconfig_node_set_str(node, "mask", rec->mask); + if (rec->level) { + levelstr = bits2level(rec->level); + iconfig_node_set_str(node, "level", levelstr); + g_free(levelstr); + } + iconfig_node_set_str(node, "pattern", rec->pattern); + if (rec->exception) iconfig_node_set_bool(node, "exception", TRUE); + if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE); + if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE); + if (rec->replies) iconfig_node_set_bool(node, "replies", TRUE); + + if (rec->channels != NULL && *rec->channels != NULL) { + node = config_node_section(node, "channels", NODE_TYPE_LIST); + iconfig_node_add_list(node, rec->channels); + } +} + +static int ignore_index(IGNORE_REC *find) +{ + GSList *tmp; + int index; + + index = 0; + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (rec->servertag != NULL) + continue; + + if (rec == find) + return index; + index++; + } + + return -1; +} + +static void ignore_remove_config(IGNORE_REC *rec) +{ + CONFIG_NODE *node; + + node = iconfig_node_traverse("ignores", FALSE); + if (node != NULL) iconfig_node_list_remove(node, ignore_index(rec)); +} + +void ignore_add_rec(IGNORE_REC *rec) +{ +#ifdef HAVE_REGEX_H + rec->regexp_compiled = !rec->regexp || rec->pattern == NULL ? FALSE : + regcomp(&rec->preg, rec->pattern, + REG_EXTENDED|REG_ICASE|REG_NOSUB) == 0; +#endif + + ignores = g_slist_append(ignores, rec); + ignore_set_config(rec); + + signal_emit("ignore created", 1, rec); +} + +static void ignore_destroy(IGNORE_REC *rec, int send_signal) +{ + ignores = g_slist_remove(ignores, rec); + if (send_signal) + signal_emit("ignore destroyed", 1, rec); + +#ifdef HAVE_REGEX_H + if (rec->regexp_compiled) regfree(&rec->preg); +#endif + if (rec->channels != NULL) g_strfreev(rec->channels); + g_free_not_null(rec->mask); + g_free_not_null(rec->servertag); + g_free_not_null(rec->pattern); + g_free(rec); + + nickmatch_rebuild(nickmatch); +} + +void ignore_update_rec(IGNORE_REC *rec) +{ + if (rec->level == 0) { + /* unignored everything */ + ignore_remove_config(rec); + ignore_destroy(rec, TRUE); + } else { + /* unignore just some levels.. */ + ignore_remove_config(rec); + ignores = g_slist_remove(ignores, rec); + + ignores = g_slist_append(ignores, rec); + ignore_set_config(rec); + + signal_emit("ignore changed", 1, rec); + nickmatch_rebuild(nickmatch); + } +} + +static int unignore_timeout(void) +{ + GSList *tmp, *next; + time_t now; + + now = time(NULL); + for (tmp = ignores; tmp != NULL; tmp = next) { + IGNORE_REC *rec = tmp->data; + + next = tmp->next; + if (rec->unignore_time > 0 && now >= rec->unignore_time) { + rec->level = 0; + ignore_update_rec(rec); + } + } + + return TRUE; +} + +static void read_ignores(void) +{ + IGNORE_REC *rec; + CONFIG_NODE *node; + GSList *tmp; + + while (ignores != NULL) + ignore_destroy(ignores->data, FALSE); + + node = iconfig_node_traverse("ignores", FALSE); + if (node == NULL) { + nickmatch_rebuild(nickmatch); + return; + } + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + rec = g_new0(IGNORE_REC, 1); + ignores = g_slist_append(ignores, rec); + + rec->mask = g_strdup(config_node_get_str(node, "mask", NULL)); + if (rec->mask != NULL && strcmp(rec->mask, "*") == 0) { + /* FIXME: remove after .98 */ + g_free(rec->mask); + rec->mask = NULL; + } + rec->pattern = g_strdup(config_node_get_str(node, "pattern", NULL)); + rec->level = level2bits(config_node_get_str(node, "level", "")); + rec->exception = config_node_get_bool(node, "exception", FALSE); + if (*config_node_get_str(node, "except_level", "") != '\0') { + /* FIXME: remove after .98 */ + rec->level = level2bits(config_node_get_str(node, "except_level", "")); + rec->exception = TRUE; + } + rec->regexp = config_node_get_bool(node, "regexp", FALSE); + rec->fullword = config_node_get_bool(node, "fullword", FALSE); + rec->replies = config_node_get_bool(node, "replies", FALSE); + + node = config_node_section(node, "channels", -1); + if (node != NULL) rec->channels = config_node_get_list(node); + } + + nickmatch_rebuild(nickmatch); +} + +static void ignore_nick_cache(GHashTable *list, CHANNEL_REC *channel, + NICK_REC *nick) +{ + GSList *tmp, *matches; + char *nickmask; + + if (nick->host == NULL) + return; /* don't check until host is known */ + + matches = NULL; + nickmask = g_strconcat(nick->nick, "!", nick->host, NULL); + for (tmp = ignores; tmp != NULL; tmp = tmp->next) { + IGNORE_REC *rec = tmp->data; + + if (ignore_match_nickmask(rec, nick->nick, nickmask) && + ignore_match_server(rec, channel->server) && + ignore_match_channel(rec, channel->name)) + matches = g_slist_append(matches, rec); + } + g_free_not_null(nickmask); + + if (matches == NULL) + g_hash_table_remove(list, nick); + else + g_hash_table_insert(list, nick, matches); +} + +void ignore_init(void) +{ + ignores = NULL; + nickmatch = nickmatch_init(ignore_nick_cache); + time_tag = g_timeout_add(1000, (GSourceFunc) unignore_timeout, NULL); + + read_ignores(); + signal_add("setup reread", (SIGNAL_FUNC) read_ignores); +} + +void ignore_deinit(void) +{ + g_source_remove(time_tag); + while (ignores != NULL) + ignore_destroy(ignores->data, TRUE); + nickmatch_deinit(nickmatch); + + signal_remove("setup reread", (SIGNAL_FUNC) read_ignores); +} diff --git a/apps/irssi/src/core/ignore.h b/apps/irssi/src/core/ignore.h new file mode 100644 index 00000000..4056062e --- /dev/null +++ b/apps/irssi/src/core/ignore.h @@ -0,0 +1,40 @@ +#ifndef __IGNORE_H +#define __IGNORE_H + +#ifdef HAVE_REGEX_H +# include +#endif + +typedef struct { + int level; /* ignore these levels */ + char *mask; /* nick mask */ + char *servertag; /* this is for autoignoring */ + char **channels; /* ignore only in these channels */ + char *pattern; /* text body must match this pattern */ + + time_t unignore_time; /* time in sec for temp ignores */ + + unsigned int exception:1; /* *don't* ignore */ + unsigned int regexp:1; + unsigned int fullword:1; + unsigned int replies:1; /* ignore replies to nick in channel */ +#ifdef HAVE_REGEX_H + unsigned int regexp_compiled:1; /* should always be TRUE, unless regexp is invalid */ + regex_t preg; +#endif +} IGNORE_REC; + +extern GSList *ignores; + +int ignore_check(SERVER_REC *server, const char *nick, const char *host, + const char *channel, const char *text, int level); + +IGNORE_REC *ignore_find(const char *servertag, const char *mask, char **channels); + +void ignore_add_rec(IGNORE_REC *rec); +void ignore_update_rec(IGNORE_REC *rec); + +void ignore_init(void); +void ignore_deinit(void); + +#endif diff --git a/apps/irssi/src/core/levels.c b/apps/irssi/src/core/levels.c new file mode 100644 index 00000000..aae506a8 --- /dev/null +++ b/apps/irssi/src/core/levels.c @@ -0,0 +1,174 @@ +/* + levels.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "levels.h" + +static const char *levels[] = { + "CRAP", + "MSGS", + "PUBLICS", + "NOTICES", + "SNOTES", + "CTCPS", + "ACTIONS", + "JOINS", + "PARTS", + "QUITS", + "KICKS", + "MODES", + "TOPICS", + "WALLOPS", + "INVITES", + "NICKS", + "DCC", + "DCCMSGS", + "CLIENTNOTICES", + "CLIENTCRAP", + "CLIENTERRORS", + "HILIGHT", + + "NOHILIGHT", + NULL +}; + +int level_get(const char *level) +{ + int n, len, match; + + if (g_strcasecmp(level, "ALL") == 0 || strcmp(level, "*") == 0) + return MSGLEVEL_ALL; + + if (g_strcasecmp(level, "NEVER") == 0) + return MSGLEVEL_NEVER; + + len = strlen(level); + if (len == 0) return 0; + + /* partial match allowed, as long as it's the only one that matches */ + match = 0; + for (n = 0; levels[n] != NULL; n++) { + if (g_strncasecmp(levels[n], level, len) == 0) { + if ((int)strlen(levels[n]) == len) { + /* full match */ + return 1L << n; + } + if (match > 0) { + /* ambiguous - abort */ + return 0; + } + match = 1L << n; + } + } + + return match; +} + +int level2bits(const char *level) +{ + char *orig, *str, *ptr; + int ret, singlelevel, negative; + + g_return_val_if_fail(level != NULL, 0); + + if (*level == '\0') + return 0; + + orig = str = g_strdup(level); + + ret = 0; + for (ptr = str; ; str++) { + if (*str == ' ') + *str++ = '\0'; + else if (*str != '\0') + continue; + + negative = *ptr == '-'; + if (*ptr == '-' || *ptr == '+') ptr++; + + singlelevel = level_get(ptr); + if (singlelevel != 0) { + ret = !negative ? (ret | singlelevel) : + (ret & ~singlelevel); + } + + while (*str == ' ') str++; + if (*str == '\0') break; + + ptr = str; + } + g_free(orig); + + return ret; +} + +char *bits2level(int bits) +{ + GString *str; + char *ret; + int n; + + if (bits == 0) + return g_strdup(""); + + if (bits == MSGLEVEL_ALL) + return g_strdup("ALL"); + + str = g_string_new(NULL); + if (bits & MSGLEVEL_NEVER) + g_string_append(str, "NEVER "); + + for (n = 0; levels[n] != NULL; n++) { + if (bits & (1L << n)) + g_string_sprintfa(str, "%s ", levels[n]); + } + if (str->len > 0) + g_string_truncate(str, str->len-1); + + ret = str->str; + g_string_free(str, FALSE); + + return ret; +} + +int combine_level(int dest, const char *src) +{ + char **list, **item, *itemname; + int itemlevel; + + g_return_val_if_fail(src != NULL, dest); + + list = g_strsplit(src, " ", -1); + for (item = list; *item != NULL; item++) { + itemname = *item + (**item == '+' || **item == '-' ? 1 : 0); + g_strup(itemname); + itemlevel = level_get(itemname); + + if (strcmp(itemname, "NONE") == 0) + dest = 0; + else if (**item == '-') + dest &= ~(itemlevel); + else + dest |= itemlevel; + } + g_strfreev(list); + + return dest; +} diff --git a/apps/irssi/src/core/levels.h b/apps/irssi/src/core/levels.h new file mode 100644 index 00000000..2d7288f7 --- /dev/null +++ b/apps/irssi/src/core/levels.h @@ -0,0 +1,45 @@ +#ifndef __LEVELS_H +#define __LEVELS_H + +/* This is pretty much IRC specific, but I think it would be easier for + other chats to try to use these same levels instead of implementing too + difficult message leveling system (which might be done if really + needed..). */ + +/* Message levels */ +#define MSGLEVEL_CRAP 0x0000001 +#define MSGLEVEL_MSGS 0x0000002 +#define MSGLEVEL_PUBLIC 0x0000004 +#define MSGLEVEL_NOTICES 0x0000008 +#define MSGLEVEL_SNOTES 0x0000010 +#define MSGLEVEL_CTCPS 0x0000020 +#define MSGLEVEL_ACTIONS 0x0000040 +#define MSGLEVEL_JOINS 0x0000080 +#define MSGLEVEL_PARTS 0x0000100 +#define MSGLEVEL_QUITS 0x0000200 +#define MSGLEVEL_KICKS 0x0000400 +#define MSGLEVEL_MODES 0x0000800 +#define MSGLEVEL_TOPICS 0x0001000 +#define MSGLEVEL_WALLOPS 0x0002000 +#define MSGLEVEL_INVITES 0x0004000 +#define MSGLEVEL_NICKS 0x0008000 +#define MSGLEVEL_DCC 0x0010000 +#define MSGLEVEL_DCCMSGS 0x0020000 +#define MSGLEVEL_CLIENTNOTICE 0x0040000 +#define MSGLEVEL_CLIENTCRAP 0x0080000 +#define MSGLEVEL_CLIENTERROR 0x0100000 +#define MSGLEVEL_HILIGHT 0x0200000 + +#define MSGLEVEL_ALL 0x03fffff + +#define MSGLEVEL_NOHILIGHT 0x1000000 /* Don't highlight this message */ +#define MSGLEVEL_NO_ACT 0x2000000 /* Don't trigger channel activity */ +#define MSGLEVEL_NEVER 0x4000000 /* never ignore / never log */ +#define MSGLEVEL_LASTLOG 0x8000000 /* never ignore / never log */ + +int level_get(const char *level); +int level2bits(const char *level); +char *bits2level(int bits); +int combine_level(int dest, const char *src); + +#endif diff --git a/apps/irssi/src/core/line-split.c b/apps/irssi/src/core/line-split.c new file mode 100644 index 00000000..b7daf275 --- /dev/null +++ b/apps/irssi/src/core/line-split.c @@ -0,0 +1,141 @@ +/* + line-split.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "misc.h" + +/* Maximum line length - split to two lines if it's longer than this. + + This is mostly to prevent excessive memory usage. Like if someone DCC + chats you, you both have very fast connections and the other side sends + you 100 megs of text without any line feeds -> irssi will (try to) + allocate 128M of memory for the line and will eventually crash when it + can't allocate any more memory. If the line is split at every 64k the + text buffer will free the old lines and the memory usage never gets + too high. */ +#define MAX_CHARS_IN_LINE 65536 + +struct _LINEBUF_REC { + int len; + int alloc; + int remove; + char *str; +}; + +static void linebuf_append(LINEBUF_REC *rec, const char *data, int len) +{ + if (rec->len+len > rec->alloc) { + rec->alloc = nearest_power(rec->len+len);; + rec->str = rec->str == NULL ? g_malloc(rec->alloc) : + g_realloc(rec->str, rec->alloc); + } + + memcpy(rec->str + rec->len, data, len); + rec->len += len; +} + +static char *linebuf_find(LINEBUF_REC *rec, char chr) +{ + int n; + + for (n = 0; n < rec->len; n++) + if (rec->str[n] == chr) return rec->str+n; + + return NULL; +} + +static int remove_newline(LINEBUF_REC *rec) +{ + char *ptr; + + ptr = linebuf_find(rec, '\n'); + if (ptr == NULL) { + /* LF wasn't found, wait for more data.. */ + if (rec->len < MAX_CHARS_IN_LINE) + return 0; + + /* line buffer is too big - force a newline. */ + linebuf_append(rec, "\n", 1); + ptr = rec->str+rec->len-1; + } + + rec->remove = (int) (ptr-rec->str)+1; + if (ptr != rec->str && ptr[-1] == '\r') { + /* remove CR too. */ + ptr--; + } + + *ptr = '\0'; + return 1; +} + +/* line-split `data'. Initially `*buffer' should contain NULL. */ +int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer) +{ + LINEBUF_REC *rec; + + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(output != NULL, -1); + g_return_val_if_fail(buffer != NULL, -1); + + if (*buffer == NULL) + *buffer = g_new0(LINEBUF_REC, 1); + rec = *buffer; + + if (rec->remove > 0) { + rec->len -= rec->remove; + g_memmove(rec->str, rec->str+rec->remove, rec->len); + rec->remove = 0; + } + + if (len > 0) + linebuf_append(rec, data, len); + else if (len < 0) { + /* connection closed.. */ + if (rec->len == 0) + return -1; + + /* no new data got but still something in buffer.. */ + len = 0; + if (linebuf_find(rec, '\n') == NULL) { + /* connection closed and last line is missing \n .. + just add it so we can see if it had + anything useful.. */ + linebuf_append(rec, "\n", 1); + } + } + + *output = rec->str; + return remove_newline(rec); +} + +void line_split_free(LINEBUF_REC *buffer) +{ + if (buffer != NULL) { + if (buffer->str != NULL) g_free(buffer->str); + g_free(buffer); + } +} + +/* Return 1 if there is no data in the buffer */ +int line_split_is_empty(LINEBUF_REC *buffer) +{ + return buffer->len == 0; +} diff --git a/apps/irssi/src/core/line-split.h b/apps/irssi/src/core/line-split.h new file mode 100644 index 00000000..7c101ddd --- /dev/null +++ b/apps/irssi/src/core/line-split.h @@ -0,0 +1,11 @@ +#ifndef __LINE_SPLIT_H +#define __LINE_SPLIT_H + +/* line-split `data'. Initially `*buffer' should contain NULL. */ +int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer); +void line_split_free(LINEBUF_REC *buffer); + +/* Return 1 if there is no data in the buffer */ +int line_split_is_empty(LINEBUF_REC *buffer); + +#endif diff --git a/apps/irssi/src/core/log.c b/apps/irssi/src/core/log.c new file mode 100644 index 00000000..368de25f --- /dev/null +++ b/apps/irssi/src/core/log.c @@ -0,0 +1,565 @@ +/* + log.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "servers.h" +#include "log.h" +#include "write-buffer.h" + +#include "lib-config/iconfig.h" +#include "settings.h" + +#define DEFAULT_LOG_FILE_CREATE_MODE 644 + +#ifdef HAVE_FCNTL +static struct flock lock; +#endif + +GSList *logs; + +static const char *log_item_types[] = { + "target", + "window", + + NULL +}; + +const char *log_timestamp; +static int log_file_create_mode; +static int rotate_tag; + +static int log_item_str2type(const char *type) +{ + int n; + + for (n = 0; log_item_types[n] != NULL; n++) { + if (g_strcasecmp(log_item_types[n], type) == 0) + return n; + } + + return -1; +} + +static void log_write_timestamp(int handle, const char *format, + const char *text, time_t stamp) +{ + struct tm *tm; + char str[256]; + + g_return_if_fail(format != NULL); + if (*format == '\0') return; + + tm = localtime(&stamp); + if (strftime(str, sizeof(str), format, tm) > 0) + write_buffer(handle, str, strlen(str)); + if (text != NULL) write_buffer(handle, text, strlen(text)); +} + +static char *log_filename(LOG_REC *log) +{ + char *str, fname[1024]; + struct tm *tm; + size_t ret; + time_t now; + + now = time(NULL); + tm = localtime(&now); + + str = convert_home(log->fname); + ret = strftime(fname, sizeof(fname), str, tm); + g_free(str); + + if (ret <= 0) { + g_warning("log_filename() : strftime() failed"); + return NULL; + } + + return g_strdup(fname); +} + +int log_start_logging(LOG_REC *log) +{ + g_return_val_if_fail(log != NULL, FALSE); + + if (log->handle != -1) + return TRUE; + + /* Append/create log file */ + g_free_not_null(log->real_fname); + log->real_fname = log_filename(log); + log->handle = log->real_fname == NULL ? -1 : + open(log->real_fname, O_WRONLY | O_APPEND | O_CREAT, + log_file_create_mode); + if (log->handle == -1) { + signal_emit("log create failed", 1, log); + log->failed = TRUE; + return FALSE; + } +#ifdef HAVE_FCNTL + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + if (fcntl(log->handle, F_SETLK, &lock) == -1 && errno == EACCES) { + close(log->handle); + log->handle = -1; + signal_emit("log locked", 1, log); + log->failed = TRUE; + return FALSE; + } +#endif + lseek(log->handle, 0, SEEK_END); + + log->opened = log->last = time(NULL); + log_write_timestamp(log->handle, + settings_get_str("log_open_string"), + "\n", log->last); + + signal_emit("log started", 1, log); + log->failed = FALSE; + return TRUE; +} + +void log_stop_logging(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + if (log->handle == -1) + return; + + signal_emit("log stopped", 1, log); + + log_write_timestamp(log->handle, + settings_get_str("log_close_string"), + "\n", time(NULL)); + +#ifdef HAVE_FCNTL + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + fcntl(log->handle, F_SETLK, &lock); +#endif + + write_buffer_flush(); + close(log->handle); + log->handle = -1; +} + +static void log_rotate_check(LOG_REC *log) +{ + char *new_fname; + + g_return_if_fail(log != NULL); + + if (log->handle == -1 || log->real_fname == NULL) + return; + + new_fname = log_filename(log); + if (strcmp(new_fname, log->real_fname) != 0) { + /* rotate log */ + log_stop_logging(log); + log_start_logging(log); + } + g_free(new_fname); +} + +void log_write_rec(LOG_REC *log, const char *str, int level) +{ + struct tm *tm; + time_t now; + int hour, day; + + g_return_if_fail(log != NULL); + g_return_if_fail(str != NULL); + + if (log->handle == -1) + return; + + now = time(NULL); + tm = localtime(&now); + hour = tm->tm_hour; + day = tm->tm_mday; + + tm = localtime(&log->last); + day -= tm->tm_mday; /* tm breaks in log_rotate_check() .. */ + if (tm->tm_hour != hour) { + /* hour changed, check if we need to rotate log file */ + log_rotate_check(log); + } + + if (day != 0) { + /* day changed */ + log_write_timestamp(log->handle, + settings_get_str("log_day_changed"), + "\n", now); + } + + log->last = now; + + if ((level & MSGLEVEL_LASTLOG) == 0) + log_write_timestamp(log->handle, log_timestamp, str, now); + else + write_buffer(log->handle, str, strlen(str)); + write_buffer(log->handle, "\n", 1); + + signal_emit("log written", 2, log, str); +} + +LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item, + const char *servertag) +{ + GSList *tmp; + + g_return_val_if_fail(log != NULL, NULL); + g_return_val_if_fail(item != NULL, NULL); + + for (tmp = log->items; tmp != NULL; tmp = tmp->next) { + LOG_ITEM_REC *rec = tmp->data; + + if (rec->type == type && g_strcasecmp(rec->name, item) == 0 && + (rec->servertag == NULL || (servertag != NULL && + g_strcasecmp(rec->servertag, servertag) == 0))) + return rec; + } + + return NULL; +} + +void log_file_write(SERVER_REC *server, const char *item, int level, + const char *str, int no_fallbacks) +{ + GSList *tmp, *fallbacks; + char *tmpstr, *servertag; + int found; + + g_return_if_fail(str != NULL); + + if (logs == NULL) + return; + + servertag = server == NULL ? NULL : server->tag; + fallbacks = NULL; found = FALSE; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (rec->handle == -1) + continue; /* log not opened yet */ + + if ((level & rec->level) == 0) + continue; + + if (rec->items == NULL) + fallbacks = g_slist_append(fallbacks, rec); + else if (item != NULL && + log_item_find(rec, LOG_ITEM_TARGET, item, + servertag) != NULL) + log_write_rec(rec, str, level); + } + + if (!found && !no_fallbacks && fallbacks != NULL) { + /* not found from any items, so write it to all main logs */ + tmpstr = (level & MSGLEVEL_PUBLIC) ? + g_strconcat(item, ": ", str, NULL) : + g_strdup(str); + + for (tmp = fallbacks; tmp != NULL; tmp = tmp->next) + log_write_rec(tmp->data, tmpstr, level); + + g_free(tmpstr); + } + g_slist_free(fallbacks); +} + +LOG_REC *log_find(const char *fname) +{ + GSList *tmp; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *rec = tmp->data; + + if (strcmp(rec->fname, fname) == 0) + return rec; + } + + return NULL; +} + +static void log_items_update_config(LOG_REC *log, CONFIG_NODE *parent) +{ + GSList *tmp; + CONFIG_NODE *node; + + parent = config_node_section(parent, "items", NODE_TYPE_LIST); + for (tmp = log->items; tmp != NULL; tmp = tmp->next) { + LOG_ITEM_REC *rec = tmp->data; + + node = config_node_section(parent, NULL, NODE_TYPE_BLOCK); + iconfig_node_set_str(node, "type", log_item_types[rec->type]); + iconfig_node_set_str(node, "name", rec->name); + iconfig_node_set_str(node, "server", rec->servertag); + } +} + +static void log_update_config(LOG_REC *log) +{ + CONFIG_NODE *node; + char *levelstr; + + if (log->temp) + return; + + node = iconfig_node_traverse("logs", TRUE); + node = config_node_section(node, log->fname, NODE_TYPE_BLOCK); + + if (log->autoopen) + iconfig_node_set_bool(node, "auto_open", TRUE); + else + iconfig_node_set_str(node, "auto_open", NULL); + + levelstr = bits2level(log->level); + iconfig_node_set_str(node, "level", levelstr); + g_free(levelstr); + + iconfig_node_set_str(node, "items", NULL); + + if (log->items != NULL) + log_items_update_config(log, node); +} + +static void log_remove_config(LOG_REC *log) +{ + iconfig_set_str("logs", log->fname, NULL); +} + +LOG_REC *log_create_rec(const char *fname, int level) +{ + LOG_REC *rec; + + g_return_val_if_fail(fname != NULL, NULL); + + rec = log_find(fname); + if (rec == NULL) { + rec = g_new0(LOG_REC, 1); + rec->fname = g_strdup(fname); + rec->real_fname = log_filename(rec); + rec->handle = -1; + } + + rec->level = level; + return rec; +} + +void log_item_add(LOG_REC *log, int type, const char *name, + const char *servertag) +{ + LOG_ITEM_REC *rec; + + g_return_if_fail(log != NULL); + g_return_if_fail(name != NULL); + + if (log_item_find(log, type, name, servertag)) + return; + + rec = g_new0(LOG_ITEM_REC, 1); + rec->type = type; + rec->name = g_strdup(name); + rec->servertag = g_strdup(servertag); + + log->items = g_slist_append(log->items, rec); +} + +void log_update(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + if (log_find(log->fname) == NULL) { + logs = g_slist_append(logs, log); + log->handle = -1; + } + + log_update_config(log); + signal_emit("log new", 1, log); +} + +void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item) +{ + log->items = g_slist_remove(log->items, item); + + g_free(item->name); + g_free_not_null(item->servertag); + g_free(item); +} + +static void log_destroy(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + if (log->handle != -1) + log_stop_logging(log); + + logs = g_slist_remove(logs, log); + signal_emit("log remove", 1, log); + + while (log->items != NULL) + log_item_destroy(log, log->items->data); + g_free(log->fname); + g_free_not_null(log->real_fname); + g_free(log); +} + +void log_close(LOG_REC *log) +{ + g_return_if_fail(log != NULL); + + log_remove_config(log); + log_destroy(log); +} + +static int sig_rotate_check(void) +{ + static int last_hour = -1; + struct tm tm; + time_t now; + + /* don't do anything until hour is changed */ + now = time(NULL); + memcpy(&tm, localtime(&now), sizeof(tm)); + if (tm.tm_hour != last_hour) { + last_hour = tm.tm_hour; + g_slist_foreach(logs, (GFunc) log_rotate_check, NULL); + } + return 1; +} + +static void log_items_read_config(CONFIG_NODE *node, LOG_REC *log) +{ + LOG_ITEM_REC *rec; + GSList *tmp; + char *item; + int type; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + item = config_node_get_str(node, "name", NULL); + type = log_item_str2type(config_node_get_str(node, "type", NULL)); + if (item == NULL || type == -1) + continue; + + rec = g_new0(LOG_ITEM_REC, 1); + rec->type = type; + rec->name = g_strdup(item); + rec->servertag = g_strdup(config_node_get_str(node, "server", NULL)); + + log->items = g_slist_append(log->items, rec); + } +} + +static void log_read_config(void) +{ + CONFIG_NODE *node; + LOG_REC *log; + GSList *tmp, *next, *fnames; + + /* close old logs, save list of open logs */ + fnames = NULL; + for (tmp = logs; tmp != NULL; tmp = next) { + log = tmp->data; + + next = tmp->next; + if (log->temp) + continue; + + if (log->handle != -1) + fnames = g_slist_append(fnames, g_strdup(log->fname)); + log_destroy(log); + } + + node = iconfig_node_traverse("logs", FALSE); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + log = g_new0(LOG_REC, 1); + logs = g_slist_append(logs, log); + + log->handle = -1; + log->fname = g_strdup(node->key); + log->autoopen = config_node_get_bool(node, "auto_open", FALSE); + log->level = level2bits(config_node_get_str(node, "level", 0)); + + node = config_node_section(node, "items", -1); + if (node != NULL) + log_items_read_config(node, log); + + if (log->autoopen || gslist_find_string(fnames, log->fname)) + log_start_logging(log); + } + + g_slist_foreach(fnames, (GFunc) g_free, NULL); + g_slist_free(fnames); +} + +static void read_settings(void) +{ + log_timestamp = settings_get_str("log_timestamp"); + log_file_create_mode = octal2dec(settings_get_int("log_create_mode")); +} + +void log_init(void) +{ + rotate_tag = g_timeout_add(60000, (GSourceFunc) sig_rotate_check, NULL); + logs = NULL; + + settings_add_int("log", "log_create_mode", + DEFAULT_LOG_FILE_CREATE_MODE); + settings_add_str("log", "log_timestamp", "%H:%M "); + settings_add_str("log", "log_open_string", + "--- Log opened %a %b %d %H:%M:%S %Y"); + settings_add_str("log", "log_close_string", + "--- Log closed %a %b %d %H:%M:%S %Y"); + settings_add_str("log", "log_day_changed", + "--- Day changed %a %b %d %Y"); + + read_settings(); + log_read_config(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add("setup reread", (SIGNAL_FUNC) log_read_config); +} + +void log_deinit(void) +{ + g_source_remove(rotate_tag); + + while (logs != NULL) + log_close(logs->data); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("setup reread", (SIGNAL_FUNC) log_read_config); +} diff --git a/apps/irssi/src/core/log.h b/apps/irssi/src/core/log.h new file mode 100644 index 00000000..7361b6a0 --- /dev/null +++ b/apps/irssi/src/core/log.h @@ -0,0 +1,56 @@ +#ifndef __LOG_H +#define __LOG_H + +enum { + LOG_ITEM_TARGET, /* channel, query, .. */ + LOG_ITEM_WINDOW_REFNUM +}; + +typedef struct { + int type; + char *name; + char *servertag; +} LOG_ITEM_REC; + +typedef struct { + char *fname; /* file name, in strftime() format */ + char *real_fname; /* the current expanded file name */ + int handle; /* file handle */ + time_t opened; + + int level; /* log only these levels */ + GSList *items; /* log only on these items */ + + time_t last; /* when last message was written */ + + unsigned int autoopen:1; /* automatically start logging at startup */ + unsigned int failed:1; /* opening log failed last time */ + unsigned int temp:1; /* don't save this to config file */ +} LOG_REC; + +extern GSList *logs; + +/* Create log record - you still need to call log_update() to actually add it + into log list */ +LOG_REC *log_create_rec(const char *fname, int level); +void log_update(LOG_REC *log); +void log_close(LOG_REC *log); +LOG_REC *log_find(const char *fname); + +void log_item_add(LOG_REC *log, int type, const char *name, + const char *servertag); +void log_item_destroy(LOG_REC *log, LOG_ITEM_REC *item); +LOG_ITEM_REC *log_item_find(LOG_REC *log, int type, const char *item, + const char *servertag); + +void log_file_write(SERVER_REC *server, const char *item, int level, + const char *str, int no_fallbacks); +void log_write_rec(LOG_REC *log, const char *str, int level); + +int log_start_logging(LOG_REC *log); +void log_stop_logging(LOG_REC *log); + +void log_init(void); +void log_deinit(void); + +#endif diff --git a/apps/irssi/src/core/masks.c b/apps/irssi/src/core/masks.c new file mode 100644 index 00000000..7014e431 --- /dev/null +++ b/apps/irssi/src/core/masks.c @@ -0,0 +1,133 @@ +/* + masks.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" +#include "misc.h" + +#include "servers.h" + +/* Returns TRUE if mask contains '!' ie. address should be checked too. + Also checks if mask contained any wildcards. */ +static int check_address(const char *mask, int *wildcards) +{ + int ret; + + ret = FALSE; + while (*mask != '\0') { + if (*mask == '!') { + if (*wildcards) return TRUE; + ret = TRUE; + } + + if (*mask == '?' || *mask == '*') { + *wildcards = TRUE; + if (ret) return TRUE; + } + mask++; + } + + return ret; +} + +static int check_mask(SERVER_REC *server, const char *mask, + const char *str, int wildcards) +{ + if (server != NULL && server->mask_match_func != NULL) { + /* use server specified mask match function */ + return server->mask_match_func(mask, str); + } + + return wildcards ? match_wildcards(mask, str) : + g_strcasecmp(mask, str) == 0; +} + +int mask_match(SERVER_REC *server, const char *mask, + const char *nick, const char *user, const char *host) +{ + char *str; + int ret, wildcards; + + g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE); + g_return_val_if_fail(mask != NULL && nick != NULL && + nick != NULL && host != NULL, FALSE); + + str = !check_address(mask, &wildcards) ? (char *) nick : + g_strdup_printf("%s!%s@%s", nick, user, host); + ret = check_mask(server, mask, str, wildcards); + if (str != nick) g_free(str); + + return ret; +} + +int mask_match_address(SERVER_REC *server, const char *mask, + const char *nick, const char *address) +{ + char *str; + int ret, wildcards; + + g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE); + g_return_val_if_fail(mask != NULL && nick != NULL, FALSE); + if (address == NULL) address = ""; + + str = !check_address(mask, &wildcards) ? (char *) nick : + g_strdup_printf("%s!%s", nick, address); + ret = check_mask(server, mask, str, wildcards); + if (str != nick) g_free(str); + + return ret; +} + +int masks_match(SERVER_REC *server, const char *masks, + const char *nick, const char *address) +{ + int (*mask_match_func)(const char *, const char *); + char **list, **tmp, *mask; + int found; + + g_return_val_if_fail(server == NULL || IS_SERVER(server), FALSE); + g_return_val_if_fail(masks != NULL && + nick != NULL && address != NULL, FALSE); + + if (*masks == '\0') + return FALSE; + + mask_match_func = server != NULL && server->mask_match_func != NULL ? + server->mask_match_func : match_wildcards; + + found = FALSE; + mask = g_strdup_printf("%s!%s", nick, address); + list = g_strsplit(masks, " ", -1); + for (tmp = list; *tmp != NULL; tmp++) { + if (g_strcasecmp(*tmp, nick) == 0) { + found = TRUE; + break; + } + + if (mask_match_func(*tmp, mask)) { + found = TRUE; + break; + } + } + g_strfreev(list); + g_free(mask); + + return found; +} diff --git a/apps/irssi/src/core/masks.h b/apps/irssi/src/core/masks.h new file mode 100644 index 00000000..b6ce20d8 --- /dev/null +++ b/apps/irssi/src/core/masks.h @@ -0,0 +1,11 @@ +#ifndef __MASKS_H +#define __MASKS_H + +int mask_match(SERVER_REC *server, const char *mask, + const char *nick, const char *user, const char *host); +int mask_match_address(SERVER_REC *server, const char *mask, + const char *nick, const char *address); +int masks_match(SERVER_REC *server, const char *masks, + const char *nick, const char *address); + +#endif diff --git a/apps/irssi/src/core/memdebug.c b/apps/irssi/src/core/memdebug.c new file mode 100644 index 00000000..213d4542 --- /dev/null +++ b/apps/irssi/src/core/memdebug.c @@ -0,0 +1,379 @@ +/* + memdebug.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include +#include + +/*#define ENABLE_BUFFER_CHECKS*/ +#define BUFFER_CHECK_SIZE 5 +#define MIN_BUFFER_CHECK_SIZE 2 + +typedef struct { + void *p; + int size; + char *file; + int line; + char *comment; +} MEM_REC; + +static GHashTable *data = NULL, *preallocs = NULL; +static const char *comment = ""; + +static void add_flow_checks(char *p, unsigned long size) +{ +#ifdef ENABLE_BUFFER_CHECKS + int n; + + for (n = 0; n < BUFFER_CHECK_SIZE; n++) + p[n] = n ^ 0x7f; + for (n = 0; n < BUFFER_CHECK_SIZE; n++) + p[size-BUFFER_CHECK_SIZE+n] = n ^ 0x7f; +#endif +} + +void ig_memcheck_rec(void *key, MEM_REC *rec) +{ + guchar *p; + int n; + + if (rec->size != INT_MIN){ + p = rec->p; + + for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++) + if (p[n] != (n ^ 0x7f)) + g_error("buffer underflow, file %s line %d!\n", rec->file, rec->line); + + for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++) + if (p[rec->size-BUFFER_CHECK_SIZE+n] != (n ^ 0x7f)) + g_error("buffer overflow, file %s line %d!\n", rec->file, rec->line); + } +} + +static void mem_check(void) +{ +#ifdef ENABLE_BUFFER_CHECKS + g_hash_table_foreach(data, (GHFunc) ig_memcheck_rec, NULL); +#endif +} + +static void data_add(char *p, int size, const char *file, int line) +{ + MEM_REC *rec; + + if (size <= 0 && size != INT_MIN) + g_error("size = %d, file %s line %d", size, file, line); + + if (data == NULL) { + data = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + preallocs = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + } + + if (g_hash_table_lookup(data, p) != NULL) + g_error("data_add() already malloc()'ed %p (in %s:%d)", p, file, line); + + rec = g_new(MEM_REC, 1); + g_hash_table_insert(data, p, rec); + + rec->p = p; + rec->size = size; + rec->file = g_strdup(file); + rec->line = line; + rec->comment = g_strdup(comment); + + if (size == INT_MIN) + g_hash_table_insert(preallocs, p-BUFFER_CHECK_SIZE, p); + else + add_flow_checks(p, size); + mem_check(); +} + +static void data_clear(char *p) +{ + MEM_REC *rec; + + if (g_hash_table_lookup(preallocs, p) != NULL) + p += BUFFER_CHECK_SIZE; + + rec = g_hash_table_lookup(data, p); + if (rec != NULL && rec->size > 0) + memset(p, 'F', rec->size); +} + +static void *data_remove(char *p, const char *file, int line) +{ + MEM_REC *rec; + + mem_check(); + + if (g_hash_table_lookup(preallocs, p) != NULL) { + g_hash_table_remove(preallocs, p); + p += BUFFER_CHECK_SIZE; + } + + rec = g_hash_table_lookup(data, p); + if (rec == NULL) { + g_warning("data_remove() data %p not found (in %s:%d)", p, file, line); + return p+BUFFER_CHECK_SIZE; + } + + g_hash_table_remove(data, p); + g_free(rec->file); + g_free(rec->comment); + g_free(rec); + + return p; +} + +void *ig_malloc(int size, const char *file, int line) +{ + char *p; + + size += BUFFER_CHECK_SIZE*2; + p = g_malloc(size); + data_add(p, size, file, line); + return (void *) (p+BUFFER_CHECK_SIZE); +} + +void *ig_malloc0(int size, const char *file, int line) +{ + char *p; + + size += BUFFER_CHECK_SIZE*2; + p = g_malloc0(size); + data_add(p, size, file, line); + return (void *) (p+BUFFER_CHECK_SIZE); +} + +void *ig_realloc(void *mem, unsigned long size, const char *file, int line) +{ + char *p, *oldmem = mem; + + size += BUFFER_CHECK_SIZE*2; + oldmem -= BUFFER_CHECK_SIZE; + data_remove(oldmem, file, line); + p = g_realloc(oldmem, size); + data_add(p, size, file, line); + return (void *) (p+BUFFER_CHECK_SIZE); +} + +char *ig_strdup(const char *str, const char *file, int line) +{ + void *p; + + if (str == NULL) return NULL; + + p = ig_malloc(strlen(str)+1, file, line); + strcpy(p, str); + + return p; +} + +char *ig_strndup(const char *str, int count, const char *file, int line) +{ + char *p; + + if (str == NULL) return NULL; + + p = ig_malloc(count+1, file, line); + strncpy(p, str, count); p[count] = '\0'; + + return p; +} + +char *ig_strconcat(const char *file, int line, const char *str, ...) +{ + guint l; + va_list args; + char *s; + char *concat; + + g_return_val_if_fail (str != NULL, NULL); + + l = 1 + strlen (str); + va_start (args, str); + s = va_arg (args, char*); + while (s) + { + l += strlen (s); + s = va_arg (args, char*); + } + va_end (args); + + concat = ig_malloc(l, file, line); + concat[0] = 0; + + strcat (concat, str); + va_start (args, str); + s = va_arg (args, char*); + while (s) + { + strcat (concat, s); + s = va_arg (args, char*); + } + va_end (args); + + return concat; +} + +char *ig_strdup_printf(const char *file, int line, const char *format, ...) +{ + char *buffer, *p; + va_list args; + + va_start (args, format); + buffer = g_strdup_vprintf (format, args); + va_end (args); + + p = ig_malloc(strlen(buffer)+1, file, line); + strcpy(p, buffer); + g_free(buffer); + + return p; +} + +char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args) +{ + char *buffer, *p; + + buffer = g_strdup_vprintf (format, args); + + p = ig_malloc(strlen(buffer)+1, file, line); + strcpy(p, buffer); + g_free(buffer); + + return p; +} + +void ig_free(void *p) +{ + char *cp = p; + + if (cp == NULL) g_error("ig_free() : trying to free NULL"); + + cp -= BUFFER_CHECK_SIZE; + data_clear(cp); + cp = data_remove(cp, "??", 0); + if (cp != NULL) g_free(cp); +} + +GString *ig_string_new(const char *file, int line, const char *str) +{ + GString *ret; + + ret = g_string_new(str); + data_add((void *) ret, INT_MIN, file, line); + return ret; +} + +void ig_string_free(const char *file, int line, GString *str, gboolean freeit) +{ + data_remove((void *) str, file, line); + if (!freeit) + data_add(str->str, INT_MIN, file, line); + + g_string_free(str, freeit); +} + +char *ig_strjoinv(const char *file, int line, const char *sepa, char **array) +{ + char *ret; + + ret = g_strjoinv(sepa, array); + data_add(ret, INT_MIN, file, line); + return ret; +} + +char *ig_dirname(const char *file, int line, const char *fname) +{ + char *ret; + + ret = g_dirname(fname); + data_add(ret, INT_MIN, file, line); + return ret; +} + +char *ig_module_build_path(const char *file, int line, const char *dir, const char *module) +{ + char *ret; + + ret = g_module_build_path(dir, module); + data_add(ret, INT_MIN, file, line); + return ret; +} + +void ig_profile_line(void *key, MEM_REC *rec) +{ + char *data; + + if (*rec->comment == '\0' && + (strcmp(rec->file, "ig_strdup_printf") == 0 || + strcmp(rec->file, "ig_strdup_vprintf") == 0 || + strcmp(rec->file, "ig_strconcat") == 0 || + strcmp(rec->file, "ig_string_free (free = FALSE)") == 0)) + data = (char *) rec->p + BUFFER_CHECK_SIZE; + else + data = rec->comment; + fprintf(stderr, "%s:%d %d bytes (%s)\n", rec->file, rec->line, rec->size, data); +} + +void ig_mem_profile(void) +{ + g_hash_table_foreach(data, (GHFunc) ig_profile_line, NULL); + g_hash_table_destroy(data); + g_hash_table_destroy(preallocs); +} + +static MEM_REC *largest[10]; + +void ig_profile_largest(void *key, MEM_REC *rec) +{ + int n; + + for (n = 0; n < 10; n++) + { + if (largest[n] == NULL || rec->size > largest[n]->size) + { + g_memmove(largest+n+1, largest+n, sizeof(void *)*(9-n)); + largest[n] = rec; + } + } +} + +void ig_mem_profile_largest(void) +{ + /*int n;*/ + + memset(&largest, 0, sizeof(MEM_REC*)*10); + /*g_hash_table_foreach(data, (GHFunc) ig_profile_largest, NULL); + + for (n = 0; n < 10 && largest[n] != NULL; n++) + { + ig_profile_line(NULL, largest[n]); + }*/ +} + +void ig_set_data(const char *data) +{ + comment = data; +} diff --git a/apps/irssi/src/core/memdebug.h b/apps/irssi/src/core/memdebug.h new file mode 100644 index 00000000..3f328805 --- /dev/null +++ b/apps/irssi/src/core/memdebug.h @@ -0,0 +1,38 @@ +#ifdef MEM_DEBUG +void ig_mem_profile(void); + +void ig_set_data(const char *data); + +void *ig_malloc(int size, const char *file, int line); +void *ig_malloc0(int size, const char *file, int line); +void *ig_realloc(void *mem, unsigned long size, const char *file, int line); +char *ig_strdup(const char *str, const char *file, int line); +char *ig_strndup(const char *str, int count, const char *file, int line); +char *ig_strconcat(const char *file, int line, const char *str, ...); +char *ig_strdup_printf(const char *file, int line, const char *format, ...) G_GNUC_PRINTF (3, 4); +char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args); +void ig_free(void *p); +GString *ig_string_new(const char *file, int line, const char *str); +void ig_string_free(const char *file, int line, GString *str, int freeit); +char *ig_strjoinv(const char *file, int line, const char *sepa, char **array); +char *ig_dirname(const char *file, int line, const char *fname); +char *ig_module_build_path(const char *file, int line, const char *dir, const char *module); + +#define g_malloc(a) ig_malloc(a, __FILE__, __LINE__) +#define g_malloc0(a) ig_malloc0(a, __FILE__, __LINE__) +#define g_free ig_free +#define g_realloc(a,b) ig_realloc(a, b, __FILE__, __LINE__) +#define g_strdup(a) ig_strdup(a, __FILE__, __LINE__) +#define g_strndup(a, b) ig_strndup(a, b, __FILE__, __LINE__) +#define g_string_new(a) ig_string_new(__FILE__, __LINE__, a) +#define g_string_free(a, b) ig_string_free(__FILE__, __LINE__, a, b) +#define g_strjoinv(a,b) ig_strjoinv(__FILE__, __LINE__, a, b) +#define g_dirname(a) ig_dirname(__FILE__, __LINE__, a) +#define g_module_build_path(a, b) ig_module_build_path(__FILE__, __LINE__, a, b) + +#ifndef __STRICT_ANSI__ +#define g_strconcat(a...) ig_strconcat(__FILE__, __LINE__, ##a) +#define g_strdup_printf(a, b...) ig_strdup_printf(__FILE__, __LINE__, a, ##b) +#define g_strdup_vprintf(a, b...) ig_strdup_vprintf(__FILE__, __LINE__, a, ##b) +#endif +#endif diff --git a/apps/irssi/src/core/misc.c b/apps/irssi/src/core/misc.c new file mode 100644 index 00000000..1af327af --- /dev/null +++ b/apps/irssi/src/core/misc.c @@ -0,0 +1,727 @@ +/* + misc.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "misc.h" +#include "pidwait.h" + +#include +#ifdef HAVE_REGEX_H +# include +#endif + +typedef struct { + int condition; + GInputFunction function; + void *data; +} IRSSI_INPUT_REC; + +static int irssi_io_invoke(GIOChannel *source, GIOCondition condition, + void *data) +{ + IRSSI_INPUT_REC *rec = data; + int icond = 0; + + if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + /* error, we have to call the function.. */ + if (rec->condition & G_IO_IN) + icond |= G_INPUT_READ; + else + icond |= G_INPUT_WRITE; + } + + if (condition & (G_IO_IN | G_IO_PRI)) + icond |= G_INPUT_READ; + if (condition & G_IO_OUT) + icond |= G_INPUT_WRITE; + + if (rec->condition & icond) + rec->function(rec->data, source, icond); + + return TRUE; +} + +int g_input_add_full(GIOChannel *source, int priority, int condition, + GInputFunction function, void *data) +{ + IRSSI_INPUT_REC *rec; + unsigned int result; + GIOCondition cond; + + rec = g_new(IRSSI_INPUT_REC, 1); + rec->condition = condition; + rec->function = function; + rec->data = data; + + cond = (GIOCondition) (G_IO_ERR|G_IO_HUP|G_IO_NVAL); + if (condition & G_INPUT_READ) + cond |= G_IO_IN|G_IO_PRI; + if (condition & G_INPUT_WRITE) + cond |= G_IO_OUT; + + result = g_io_add_watch_full(source, priority, cond, + irssi_io_invoke, rec, g_free); + + return result; +} + +int g_input_add(GIOChannel *source, int condition, + GInputFunction function, void *data) +{ + return g_input_add_full(source, G_PRIORITY_DEFAULT, condition, + function, data); +} + +int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2) +{ + if (tv1->tv_sec < tv2->tv_sec) + return -1; + if (tv1->tv_sec > tv2->tv_sec) + return 1; + + return tv1->tv_usec < tv2->tv_usec ? -1 : + tv1->tv_usec > tv2->tv_usec ? 1 : 0; +} + +long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2) +{ + long secs, usecs; + + secs = tv1->tv_sec - tv2->tv_sec; + usecs = tv1->tv_usec - tv2->tv_usec; + if (usecs < 0) { + usecs += 1000000; + secs--; + } + usecs = usecs/1000 + secs * 1000; + + return usecs; +} + +int find_substr(const char *list, const char *item) +{ + const char *ptr; + + g_return_val_if_fail(list != NULL, FALSE); + g_return_val_if_fail(item != NULL, FALSE); + + if (*item == '\0') + return FALSE; + + for (;;) { + while (isspace((gint) *list)) list++; + if (*list == '\0') break; + + ptr = strchr(list, ' '); + if (ptr == NULL) ptr = list+strlen(list); + + if (g_strncasecmp(list, item, ptr-list) == 0 && + item[ptr-list] == '\0') + return TRUE; + + list = ptr; + } + + return FALSE; +} + +int strarray_length(char **array) +{ + int len; + + g_return_val_if_fail(array != NULL, 0); + + len = 0; + while (*array) { + len++; + array++; + } + return len; +} + +int strarray_find(char **array, const char *item) +{ + char **tmp; + int index; + + g_return_val_if_fail(array != NULL, 0); + g_return_val_if_fail(item != NULL, 0); + + index = 0; + for (tmp = array; *tmp != NULL; tmp++, index++) { + if (g_strcasecmp(*tmp, item) == 0) + return index; + } + + return -1; +} + +int execute(const char *cmd) +{ + char **args; +#ifndef WIN32 + int pid; +#endif + + g_return_val_if_fail(cmd != NULL, -1); + +#ifndef WIN32 + pid = fork(); + if (pid == -1) return FALSE; + if (pid != 0) { + pidwait_add(pid); + return pid; + } + + args = g_strsplit(cmd, " ", -1); + execvp(args[0], args); + g_strfreev(args); + + _exit(99); + return -1; +#else + args = g_strsplit(cmd, " ", -1); + _spawnvp(_P_DETACH, args[0], args); + g_strfreev(args); + return 0; +#endif +} + +GSList *gslist_find_string(GSList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (strcmp(list->data, key) == 0) return list; + + return NULL; +} + +GSList *gslist_find_icase_string(GSList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (g_strcasecmp(list->data, key) == 0) return list; + + return NULL; +} + +void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data) +{ + void *ret; + + while (list != NULL) { + ret = func(list->data, (void *) data); + if (ret != NULL) return ret; + + list = list->next; + } + + return NULL; +} + +/* `list' contains pointer to structure with a char* to string. */ +char *gslistptr_to_string(GSList *list, int offset, const char *delimiter) +{ + GString *str; + char **data, *ret; + + str = g_string_new(NULL); + while (list != NULL) { + data = G_STRUCT_MEMBER_P(list->data, offset); + + if (str->len != 0) g_string_append(str, delimiter); + g_string_append(str, *data); + list = list->next; + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +/* `list' contains char* */ +char *gslist_to_string(GSList *list, const char *delimiter) +{ + GString *str; + char *ret; + + str = g_string_new(NULL); + while (list != NULL) { + if (str->len != 0) g_string_append(str, delimiter); + g_string_append(str, list->data); + + list = list->next; + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +void hash_save_key(char *key, void *value, GSList **list) +{ + *list = g_slist_append(*list, key); +} + +/* save all keys in hash table to linked list - you shouldn't remove any + items while using this list, use g_slist_free() after you're done with it */ +GSList *hashtable_get_keys(GHashTable *hash) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(hash, (GHFunc) hash_save_key, &list); + return list; +} + +GList *glist_find_string(GList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (strcmp(list->data, key) == 0) return list; + + return NULL; +} + +GList *glist_find_icase_string(GList *list, const char *key) +{ + for (list = list; list != NULL; list = list->next) + if (g_strcasecmp(list->data, key) == 0) return list; + + return NULL; +} + +char *stristr(const char *data, const char *key) +{ + const char *max; + int keylen, datalen, pos; + + keylen = strlen(key); + datalen = strlen(data); + + if (keylen > datalen) + return NULL; + if (keylen == 0) + return (char *) data; + + max = data+datalen-keylen; + pos = 0; + while (data <= max) { + if (key[pos] == '\0') + return (char *) data; + + if (toupper(data[pos]) == toupper(key[pos])) + pos++; + else { + data++; + pos = 0; + } + } + + return NULL; +} + +#define isbound(c) \ + ((unsigned char) (c) < 128 && \ + (isspace((int) (c)) || ispunct((int) (c)))) + +char *strstr_full_case(const char *data, const char *key, int icase) +{ + const char *start, *max; + int keylen, datalen, pos, match; + + keylen = strlen(key); + datalen = strlen(data); + + if (keylen > datalen) + return NULL; + if (keylen == 0) + return (char *) data; + + max = data+datalen-keylen; + start = data; pos = 0; + while (data <= max) { + if (key[pos] == '\0') { + if (data[pos] != '\0' && !isbound(data[pos])) { + data++; + pos = 0; + continue; + } + return (char *) data; + } + + match = icase ? (toupper(data[pos]) == toupper(key[pos])) : + data[pos] == key[pos]; + + if (match && (pos != 0 || data == start || isbound(data[-1]))) + pos++; + else { + data++; + pos = 0; + } + } + + return NULL; +} + +char *strstr_full(const char *data, const char *key) +{ + return strstr_full_case(data, key, FALSE); +} + +char *stristr_full(const char *data, const char *key) +{ + return strstr_full_case(data, key, TRUE); +} + +int regexp_match(const char *str, const char *regexp) +{ +#ifdef HAVE_REGEX_H + regex_t preg; + int ret; + + if (regcomp(&preg, regexp, REG_EXTENDED|REG_ICASE|REG_NOSUB) != 0) + return 0; + + ret = regexec(&preg, str, 0, NULL, 0); + regfree(&preg); + + return ret == 0; +#else + return FALSE; +#endif +} + +/* Create the directory and all it's parent directories */ +int mkpath(const char *path, int mode) +{ + struct stat statbuf; + const char *p; + char *dir; + + g_return_val_if_fail(path != NULL, -1); + + p = g_path_skip_root((char *) path); + if (p == NULL) { + /* not a full path, maybe not what we wanted + but continue anyway.. */ + p = path; + } + for (;;) { + if (*p != G_DIR_SEPARATOR && *p != '\0') { + p++; + continue; + } + + dir = g_strndup(path, (int) (p-path)); + if (stat(dir, &statbuf) != 0) { +#ifndef WIN32 + if (mkdir(dir, mode) == -1) { +#else + if (_mkdir(dir) == -1) { +#endif + g_free(dir); + return -1; + } + } + g_free(dir); + + if (*p++ == '\0') + break; + } + + return 0; +} + +/* convert ~/ to $HOME */ +char *convert_home(const char *path) +{ + return *path == '~' && (*(path+1) == '/' || *(path+1) == '\0') ? + g_strconcat(g_get_home_dir(), path+1, NULL) : + g_strdup(path); +} + +int g_istr_equal(gconstpointer v, gconstpointer v2) +{ + return g_strcasecmp((const char *) v, (const char *) v2) == 0; +} + +int g_istr_cmp(gconstpointer v, gconstpointer v2) +{ + return g_strcasecmp((const char *) v, (const char *) v2); +} + +/* a char* hash function from ASU */ +unsigned int g_istr_hash(gconstpointer v) +{ + const char *s = (const char *) v; + unsigned int h = 0, g; + + while (*s != '\0') { + h = (h << 4) + toupper(*s); + if ((g = h & 0xf0000000UL)) { + h = h ^ (g >> 24); + h = h ^ g; + } + s++; + } + + return h /* % M */; +} + +/* Find `mask' from `data', you can use * and ? wildcards. */ +int match_wildcards(const char *cmask, const char *data) +{ + char *mask, *newmask, *p1, *p2; + int ret; + + newmask = mask = g_strdup(cmask); + for (; *mask != '\0' && *data != '\0'; mask++) { + if (*mask != '*') { + if (*mask != '?' && toupper(*mask) != toupper(*data)) + break; + + data++; + continue; + } + + while (*mask == '?' || *mask == '*') mask++; + if (*mask == '\0') { + data += strlen(data); + break; + } + + p1 = strchr(mask, '*'); + p2 = strchr(mask, '?'); + if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2; + + if (p1 != NULL) *p1 = '\0'; + + data = stristr(data, mask); + if (data == NULL) break; + + data += strlen(mask); + mask += strlen(mask)-1; + + if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*'; + } + + while (*mask == '*') mask++; + + ret = data != NULL && *data == '\0' && *mask == '\0'; + g_free(newmask); + + return ret; +} + +/* Return TRUE if all characters in `str' are numbers. + Stop when `end_char' is found from string. */ +int is_numeric(const char *str, char end_char) +{ + g_return_val_if_fail(str != NULL, FALSE); + + if (*str == '\0' || *str == end_char) + return FALSE; + + while (*str != '\0' && *str != end_char) { + if (!isdigit(*str)) return FALSE; + str++; + } + + return TRUE; +} + +/* replace all `from' chars in string to `to' chars. returns `str' */ +char *replace_chars(char *str, char from, char to) +{ + char *p; + + for (p = str; *p != '\0'; p++) { + if (*p == from) *p = to; + } + return str; +} + +int octal2dec(int octal) +{ + int dec, n; + + dec = 0; n = 1; + while (octal != 0) { + dec += n*(octal%10); + octal /= 10; n *= 8; + } + + return dec; +} + +int dec2octal(int decimal) +{ + int octal, pos; + + octal = 0; pos = 0; + while (decimal > 0) { + octal += (decimal & 7)*(pos == 0 ? 1 : pos); + decimal /= 8; + pos += 10; + } + + return octal; +} + +/* convert all low-ascii (<32) to ^ combinations */ +char *show_lowascii(const char *channel) +{ + char *str, *p; + + str = p = g_malloc(strlen(channel)*2+1); + while (*channel != '\0') { + if ((unsigned char) *channel >= 32) + *p++ = *channel; + else { + *p++ = '^'; + *p++ = *channel + 'A'-1; + } + channel++; + } + *p = '\0'; + + return str; +} + +/* Get time in human readable form with localtime() + asctime() */ +char *my_asctime(time_t t) +{ + struct tm *tm; + char *str; + int len; + + tm = localtime(&t); + str = g_strdup(asctime(tm)); + + len = strlen(str); + if (len > 0) str[len-1] = '\0'; + return str; +} + +/* Returns number of columns needed to print items. + save_column_widths is filled with length of each column. */ +int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func, + int max_width, int max_columns, + int item_extra, int item_min_size, + int **save_column_widths, int *rows) +{ + GSList *tmp; + int **columns, *columns_width, *columns_rows; + int item_pos, items_count; + int ret, len, max_len, n, col; + + items_count = g_slist_length(items); + if (items_count == 0) { + *save_column_widths = NULL; + *rows = 0; + return 0; + } + + len = max_width/(item_extra+item_min_size); + if (len <= 0) len = 1; + if (max_columns <= 0 || len < max_columns) + max_columns = len; + + columns = g_new0(int *, max_columns); + columns_width = g_new0(int, max_columns); + columns_rows = g_new0(int, max_columns); + + for (n = 1; n < max_columns; n++) { + columns[n] = g_new0(int, n+1); + columns_rows[n] = items_count <= n+1 ? 1 : + (items_count+n)/(n+1); + } + + /* for each possible column count, save the column widths and + find the biggest column count that fits to screen. */ + item_pos = 0; max_len = 0; + for (tmp = items; tmp != NULL; tmp = tmp->next) { + len = item_extra+len_func(tmp->data); + if (max_len < len) + max_len = len; + + for (n = 1; n < max_columns; n++) { + if (columns_width[n] > max_width) + continue; /* too wide */ + + col = item_pos/columns_rows[n]; + if (columns[n][col] < len) { + columns_width[n] += len-columns[n][col]; + columns[n][col] = len; + } + } + + item_pos++; + } + + for (n = max_columns-1; n >= 1; n--) { + if (columns_width[n] <= max_width && + columns[n][n] > 0) + break; + } + ret = n+1; + + *save_column_widths = g_new(int, ret); + if (ret == 1) { + **save_column_widths = max_len; + *rows = 1; + } else { + memcpy(*save_column_widths, columns[ret-1], sizeof(int)*ret); + *rows = columns_rows[ret-1]; + } + + for (n = 1; n < max_columns; n++) + g_free(columns[n]); + g_free(columns_width); + g_free(columns_rows); + g_free(columns); + + return ret; +} + +/* Return a column sorted copy of a list. */ +GSList *columns_sort_list(GSList *list, int rows) +{ + GSList *tmp, *sorted; + int row, skip; + + if (list == NULL || rows == 0) + return list; + + sorted = NULL; + + for (row = 0; row < rows; row++) { + tmp = g_slist_nth(list, row); + skip = 1; + for (; tmp != NULL; tmp = tmp->next) { + if (--skip == 0) { + skip = rows; + sorted = g_slist_append(sorted, tmp->data); + } + } + } + + g_return_val_if_fail(g_slist_length(sorted) == + g_slist_length(list), sorted); + return sorted; +} diff --git a/apps/irssi/src/core/misc.h b/apps/irssi/src/core/misc.h new file mode 100644 index 00000000..45c8ce11 --- /dev/null +++ b/apps/irssi/src/core/misc.h @@ -0,0 +1,104 @@ +#ifndef __MISC_H +#define __MISC_H + +/* `str' should be type char[MAX_INT_STRLEN] */ +#define ltoa(str, num) \ + g_snprintf(str, sizeof(str), "%d", num) + +typedef void* (*FOREACH_FIND_FUNC) (void *item, void *data); +typedef int (*COLUMN_LEN_FUNC)(void *data); + +static inline int nearest_power(int num) +{ + int n = 1; + + while (n < num) n <<= 1; + return n; +} + +/* Returns 1 if tv1 > tv2, -1 if tv2 > tv1 or 0 if they're equal. */ +int g_timeval_cmp(const GTimeVal *tv1, const GTimeVal *tv2); +/* Returns "tv1 - tv2", returns the result in milliseconds. Note that + if the difference is too large, the result might be invalid. */ +long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2); + +/* find `item' from a space separated `list' */ +int find_substr(const char *list, const char *item); +/* return how many items `array' has */ +int strarray_length(char **array); +/* return index of `item' in `array' or -1 if not found */ +int strarray_find(char **array, const char *item); + +int execute(const char *cmd); /* returns pid or -1 = error */ + +GSList *gslist_find_string(GSList *list, const char *key); +GSList *gslist_find_icase_string(GSList *list, const char *key); +GList *glist_find_string(GList *list, const char *key); +GList *glist_find_icase_string(GList *list, const char *key); + +void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, const void *data); + +/* `list' contains pointer to structure with a char* to string. */ +char *gslistptr_to_string(GSList *list, int offset, const char *delimiter); +/* `list' contains char* */ +char *gslist_to_string(GSList *list, const char *delimiter); + +/* save all keys in hash table to linked list - you shouldn't remove any + items while using this list, use g_slist_free() after you're done with it */ +GSList *hashtable_get_keys(GHashTable *hash); + +/* strstr() with case-ignoring */ +char *stristr(const char *data, const char *key); + +/* like strstr(), but matches only for full words. + `icase' specifies if match is case sensitive */ +char *strstr_full_case(const char *data, const char *key, int icase); +char *strstr_full(const char *data, const char *key); +char *stristr_full(const char *data, const char *key); + +/* easy way to check if regexp matches */ +int regexp_match(const char *str, const char *regexp); + +/* Create the directory and all it's parent directories */ +int mkpath(const char *path, int mode); +/* convert ~/ to $HOME */ +char *convert_home(const char *path); + +/* Case-insensitive string hash functions */ +int g_istr_equal(gconstpointer v, gconstpointer v2); +unsigned int g_istr_hash(gconstpointer v); + +/* Case-insensitive GCompareFunc func */ +int g_istr_cmp(gconstpointer v, gconstpointer v2); + +/* Find `mask' from `data', you can use * and ? wildcards. */ +int match_wildcards(const char *mask, const char *data); + +/* Return TRUE if all characters in `str' are numbers. + Stop when `end_char' is found from string. */ +int is_numeric(const char *str, char end_char); + +/* replace all `from' chars in string to `to' chars. returns `str' */ +char *replace_chars(char *str, char from, char to); + +/* octal <-> decimal conversions */ +int octal2dec(int octal); +int dec2octal(int decimal); + +/* convert all low-ascii (<32) to ^ combinations */ +char *show_lowascii(const char *channel); + +/* Get time in human readable form with localtime() + asctime() */ +char *my_asctime(time_t t); + +/* Returns number of columns needed to print items. + save_column_widths is filled with length of each column. */ +int get_max_column_count(GSList *items, COLUMN_LEN_FUNC len_func, + int max_width, int max_columns, + int item_extra, int item_min_size, + int **save_column_widths, int *rows); + +/* Return a column sorted copy of a list. */ +GSList *columns_sort_list(GSList *list, int rows); + +#endif diff --git a/apps/irssi/src/core/module.h b/apps/irssi/src/core/module.h new file mode 100644 index 00000000..89784389 --- /dev/null +++ b/apps/irssi/src/core/module.h @@ -0,0 +1,3 @@ +#include "common.h" + +#define MODULE_NAME "core" diff --git a/apps/irssi/src/core/modules.c b/apps/irssi/src/core/modules.c new file mode 100644 index 00000000..9797a059 --- /dev/null +++ b/apps/irssi/src/core/modules.c @@ -0,0 +1,445 @@ +/* + modules.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "signals.h" + +#include "commands.h" +#include "settings.h" + +GSList *modules; + +static GHashTable *uniqids, *uniqstrids; +static GHashTable *idlookup, *stridlookup; +static int next_uniq_id; + +void *module_check_cast(void *object, int type_pos, const char *id) +{ + return object == NULL || module_find_id(id, + G_STRUCT_MEMBER(int, object, type_pos)) == -1 ? NULL : object; +} + +void *module_check_cast_module(void *object, int type_pos, + const char *module, const char *id) +{ + const char *str; + + if (object == NULL) + return NULL; + + str = module_find_id_str(module, + G_STRUCT_MEMBER(int, object, type_pos)); + return str == NULL || strcmp(str, id) != 0 ? NULL : object; +} + +/* return unique number across all modules for `id' */ +int module_get_uniq_id(const char *module, int id) +{ + GHashTable *ids; + gpointer origkey, uniqid, idp; + int ret; + + g_return_val_if_fail(module != NULL, -1); + + ids = g_hash_table_lookup(idlookup, module); + if (ids == NULL) { + /* new module */ + ids = g_hash_table_new((GHashFunc) g_direct_hash, + (GCompareFunc) g_direct_equal); + g_hash_table_insert(idlookup, g_strdup(module), ids); + } + + idp = GINT_TO_POINTER(id); + if (!g_hash_table_lookup_extended(ids, idp, &origkey, &uniqid)) { + /* not found */ + ret = next_uniq_id++; + g_hash_table_insert(ids, idp, GINT_TO_POINTER(ret)); + g_hash_table_insert(uniqids, GINT_TO_POINTER(ret), idp); + } else { + ret = GPOINTER_TO_INT(uniqid); + } + + return ret; +} + +/* return unique number across all modules for `id' */ +int module_get_uniq_id_str(const char *module, const char *id) +{ + GHashTable *ids; + gpointer origkey, uniqid; + int ret; + + g_return_val_if_fail(module != NULL, -1); + + ids = g_hash_table_lookup(stridlookup, module); + if (ids == NULL) { + /* new module */ + ids = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + g_hash_table_insert(stridlookup, g_strdup(module), ids); + } + + if (!g_hash_table_lookup_extended(ids, id, &origkey, &uniqid)) { + /* not found */ + char *saveid; + + saveid = g_strdup(id); + ret = next_uniq_id++; + g_hash_table_insert(ids, saveid, GINT_TO_POINTER(ret)); + g_hash_table_insert(uniqstrids, GINT_TO_POINTER(ret), saveid); + } else { + ret = GPOINTER_TO_INT(uniqid); + } + + return ret; +} + +/* returns the original module specific id, -1 = not found */ +int module_find_id(const char *module, int uniqid) +{ + GHashTable *idlist; + gpointer origkey, id; + int ret; + + g_return_val_if_fail(module != NULL, -1); + + if (!g_hash_table_lookup_extended(uniqids, GINT_TO_POINTER(uniqid), + &origkey, &id)) + return -1; + + /* check that module matches */ + idlist = g_hash_table_lookup(idlookup, module); + if (idlist == NULL) + return -1; + + ret = GPOINTER_TO_INT(id); + if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) || + GPOINTER_TO_INT(id) != uniqid) + ret = -1; + + return ret; +} + +/* returns the original module specific id, NULL = not found */ +const char *module_find_id_str(const char *module, int uniqid) +{ + GHashTable *idlist; + gpointer origkey, id; + const char *ret; + + g_return_val_if_fail(module != NULL, NULL); + + if (!g_hash_table_lookup_extended(uniqstrids, GINT_TO_POINTER(uniqid), + &origkey, &id)) + return NULL; + + /* check that module matches */ + idlist = g_hash_table_lookup(stridlookup, module); + if (idlist == NULL) + return NULL; + + ret = id; + if (!g_hash_table_lookup_extended(idlist, id, &origkey, &id) || + GPOINTER_TO_INT(id) != uniqid) + ret = NULL; + + return ret; +} + +static void uniq_destroy(gpointer key, gpointer value) +{ + g_hash_table_remove(uniqids, value); +} + +static void uniq_destroy_str(gpointer key, gpointer value) +{ + g_hash_table_remove(uniqstrids, value); + g_free(key); +} + +/* Destroy unique IDs from `module'. This function is automatically called + when module is destroyed with module's name as the parameter. */ +void module_uniq_destroy(const char *module) +{ + GHashTable *idlist; + gpointer key; + + if (g_hash_table_lookup_extended(idlookup, module, &key, + (gpointer *) &idlist)) { + g_hash_table_remove(idlookup, key); + g_free(key); + + g_hash_table_foreach(idlist, (GHFunc) uniq_destroy, NULL); + g_hash_table_destroy(idlist); + } + + if (g_hash_table_lookup_extended(stridlookup, module, &key, + (gpointer *) &idlist)) { + g_hash_table_remove(stridlookup, key); + g_free(key); + + g_hash_table_foreach(idlist, (GHFunc) uniq_destroy_str, NULL); + g_hash_table_destroy(idlist); + } +} + +MODULE_REC *module_find(const char *name) +{ + GSList *tmp; + + for (tmp = modules; tmp != NULL; tmp = tmp->next) { + MODULE_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +#ifdef HAVE_GMODULE +static char *module_get_name(const char *path, int *start, int *end) +{ + const char *name; + char *module_name, *ptr; + + name = NULL; + if (g_path_is_absolute(path)) { + name = strrchr(path, G_DIR_SEPARATOR); + if (name != NULL) name++; + } + + if (name == NULL) + name = path; + + if (strncmp(name, "lib", 3) == 0) + name += 3; + + module_name = g_strdup(name); + ptr = strchr(module_name, '.'); + if (ptr != NULL) *ptr = '\0'; + + *start = (int) (name-path); + *end = *start + (ptr == NULL ? strlen(name) : + (int) (module_name-ptr)); + + return module_name; +} + +static GModule *module_open(const char *name) +{ + struct stat statbuf; + GModule *module; + char *path, *str; + + if (g_path_is_absolute(name) || + (*name == '.' && name[1] == G_DIR_SEPARATOR)) + path = g_strdup(name); + else { + /* first try from home dir */ + str = g_strdup_printf("%s/.silc/modules", g_get_home_dir()); + path = g_module_build_path(str, name); + g_free(str); + + if (stat(path, &statbuf) == 0) { + module = g_module_open(path, (GModuleFlags) 0); + g_free(path); + return module; + } + + /* module not found from home dir, try global module dir */ + g_free(path); + path = g_module_build_path(MODULEDIR, name); + } + + module = g_module_open(path, (GModuleFlags) 0); + g_free(path); + return module; +} + +#define module_error(error, module, text) \ + signal_emit("module error", 3, GINT_TO_POINTER(error), module, text) + +static int module_load_name(const char *path, const char *name, int silent) +{ + void (*module_init) (void); + GModule *gmodule; + MODULE_REC *rec; + char *initfunc; + + gmodule = module_open(path); + if (gmodule == NULL) { + if (!silent) { + module_error(MODULE_ERROR_LOAD, name, + g_module_error()); + } + return FALSE; + } + + /* get the module's init() function */ + initfunc = g_strconcat(name, "_init", NULL); + if (!g_module_symbol(gmodule, initfunc, (gpointer *) &module_init)) { + if (!silent) + module_error(MODULE_ERROR_INVALID, name, NULL); + g_module_close(gmodule); + g_free(initfunc); + return FALSE; + } + g_free(initfunc); + + rec = g_new0(MODULE_REC, 1); + rec->name = g_strdup(name); + rec->gmodule = gmodule; + modules = g_slist_append(modules, rec); + + module_init(); + settings_check_module(name); + + signal_emit("module loaded", 1, rec); + return TRUE; +} +#endif + +/* Load module - automatically tries to load also the related non-core + modules given in `prefixes' (like irc, fe, fe_text, ..) */ +int module_load(const char *path, char **prefixes) +{ +#ifdef HAVE_GMODULE + GString *realpath; + char *name, *pname; + int ret, start, end; + + g_return_val_if_fail(path != NULL, FALSE); + + if (!g_module_supported()) + return FALSE; + + name = module_get_name(path, &start, &end); + if (module_find(name)) { + module_error(MODULE_ERROR_ALREADY_LOADED, name, NULL); + g_free(name); + return FALSE; + } + + /* load "module_core" instead of "module" if it exists */ + realpath = g_string_new(path); + g_string_insert(realpath, end, "_core"); + + pname = g_strconcat(name, "_core", NULL); + ret = module_load_name(realpath->str, pname, TRUE); + g_free(pname); + + if (!ret) { + /* load "module" - complain if it's not found */ + ret = module_load_name(path, name, FALSE); + } else if (prefixes != NULL) { + /* load all the "prefix modules", like the fe-common, irc, + etc. part of the module */ + while (*prefixes != NULL) { + g_string_assign(realpath, path); + g_string_insert(realpath, start, "_"); + g_string_insert(realpath, start, *prefixes); + + pname = g_strconcat(*prefixes, "_", name, NULL); + module_load_name(realpath->str, pname, TRUE); + g_free(pname); + + prefixes++; + } + } + + g_string_free(realpath, TRUE); + g_free(name); + return ret; +#else + return FALSE; +#endif +} + +void module_unload(MODULE_REC *module) +{ +#ifdef HAVE_GMODULE + void (*module_deinit) (void); + char *deinitfunc; + + g_return_if_fail(module != NULL); + + modules = g_slist_remove(modules, module); + + signal_emit("module unloaded", 1, module); + + /* call the module's deinit() function */ + deinitfunc = g_strconcat(module->name, "_deinit", NULL); + if (g_module_symbol(module->gmodule, deinitfunc, + (gpointer *) &module_deinit)) + module_deinit(); + g_free(deinitfunc); + + settings_remove_module(module->name); + commands_remove_module(module->name); + signals_remove_module(module->name); + + g_module_close(module->gmodule); + g_free(module->name); + g_free(module); +#endif +} + +static void uniq_get_modules(char *key, void *value, GSList **list) +{ + *list = g_slist_append(*list, g_strdup(key)); +} + +void modules_init(void) +{ + modules = NULL; + + idlookup = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + uniqids = g_hash_table_new((GHashFunc) g_direct_hash, + (GCompareFunc) g_direct_equal); + + stridlookup = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + uniqstrids = g_hash_table_new((GHashFunc) g_direct_hash, + (GCompareFunc) g_direct_equal); + next_uniq_id = 0; +} + +void modules_deinit(void) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(idlookup, (GHFunc) uniq_get_modules, &list); + g_hash_table_foreach(stridlookup, (GHFunc) uniq_get_modules, &list); + + while (list != NULL) { + module_uniq_destroy(list->data); + g_free(list->data); + list = g_slist_remove(list, list->data); + } + + g_hash_table_destroy(idlookup); + g_hash_table_destroy(stridlookup); + g_hash_table_destroy(uniqids); + g_hash_table_destroy(uniqstrids); +} diff --git a/apps/irssi/src/core/modules.h b/apps/irssi/src/core/modules.h new file mode 100644 index 00000000..7a83819f --- /dev/null +++ b/apps/irssi/src/core/modules.h @@ -0,0 +1,64 @@ +#ifndef __MODULES_H +#define __MODULES_H + +#define MODULE_DATA_INIT(rec) \ + (rec)->module_data = g_hash_table_new(g_str_hash, g_str_equal) + +#define MODULE_DATA_DEINIT(rec) \ + g_hash_table_destroy((rec)->module_data) + +#define MODULE_DATA_SET(rec, data) \ + g_hash_table_insert((rec)->module_data, MODULE_NAME, data) + +#define MODULE_DATA(rec) \ + g_hash_table_lookup((rec)->module_data, MODULE_NAME) + +enum { + MODULE_ERROR_ALREADY_LOADED, + MODULE_ERROR_LOAD, + MODULE_ERROR_INVALID +}; + +typedef struct { + char *name; +#ifdef HAVE_GMODULE + GModule *gmodule; +#endif +} MODULE_REC; + +extern GSList *modules; + +MODULE_REC *module_find(const char *name); + +/* Load module - automatically tries to load also the related non-core + modules given in `prefixes' (like irc, fe, fe_text, ..) */ +int module_load(const char *path, char **prefixes); +void module_unload(MODULE_REC *module); + +#define MODULE_CHECK_CAST(object, cast, type_field, id) \ + ((cast *) module_check_cast(object, offsetof(cast, type_field), id)) +#define MODULE_CHECK_CAST_MODULE(object, cast, type_field, module, id) \ + ((cast *) module_check_cast_module(object, \ + offsetof(cast, type_field), module, id)) +void *module_check_cast(void *object, int type_pos, const char *id); +void *module_check_cast_module(void *object, int type_pos, + const char *module, const char *id); + +/* return unique number across all modules for `id' */ +int module_get_uniq_id(const char *module, int id); +/* return unique number across all modules for `id'. */ +int module_get_uniq_id_str(const char *module, const char *id); + +/* returns the original module specific id, -1 = not found */ +int module_find_id(const char *module, int uniqid); +/* returns the original module specific id, NULL = not found */ +const char *module_find_id_str(const char *module, int uniqid); + +/* Destroy unique IDs from `module'. This function is automatically called + when module is destroyed with module's name as the parameter. */ +void module_uniq_destroy(const char *module); + +void modules_init(void); +void modules_deinit(void); + +#endif diff --git a/apps/irssi/src/core/net-disconnect.c b/apps/irssi/src/core/net-disconnect.c new file mode 100644 index 00000000..b0e9535b --- /dev/null +++ b/apps/irssi/src/core/net-disconnect.c @@ -0,0 +1,158 @@ +/* + net-disconnect.c : + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" + +/* when quitting, wait for max. 5 seconds before forcing to close the socket */ +#define MAX_QUIT_CLOSE_WAIT 5 + +/* wait for max. 2 minutes for other side to close the socket */ +#define MAX_CLOSE_WAIT (60*2) + +typedef struct { + time_t created; + GIOChannel *handle; + int tag; +} NET_DISCONNECT_REC; + +static GSList *disconnects; + +static int timeout_tag; + +static void net_disconnect_remove(NET_DISCONNECT_REC *rec) +{ + disconnects = g_slist_remove(disconnects, rec); + + g_source_remove(rec->tag); + g_free(rec); +} + +static void sig_disconnect(NET_DISCONNECT_REC *rec) +{ + char buf[512]; + int count, ret; + + /* check if there's any data waiting in socket. read max. 5kB so + if server just keeps sending us stuff we won't get stuck */ + count = 0; + do { + ret = net_receive(rec->handle, buf, sizeof(buf)); + if (ret == -1) { + /* socket was closed */ + net_disconnect_remove(rec); + } + count++; + } while (ret == sizeof(buf) && count < 10); +} + +static int sig_timeout_disconnect(void) +{ + NET_DISCONNECT_REC *rec; + GSList *tmp, *next; + time_t now; + + /* check if we've waited enough for sockets to close themselves */ + now = time(NULL); + for (tmp = disconnects; tmp != NULL; tmp = next) { + rec = tmp->data; + next = tmp->next; + + if (rec->created+MAX_CLOSE_WAIT <= now) + net_disconnect_remove(rec); + } + + if (disconnects == NULL) { + /* no more sockets in disconnect queue, stop calling this + function */ + timeout_tag = -1; + } + return disconnects != NULL; +} + +/* Try to let the other side close the connection, if it still isn't + disconnected after certain amount of time, close it ourself */ +void net_disconnect_later(GIOChannel *handle) +{ + NET_DISCONNECT_REC *rec; + + rec = g_new(NET_DISCONNECT_REC, 1); + rec->created = time(NULL); + rec->handle = handle; + rec->tag = g_input_add(handle, G_INPUT_READ, + (GInputFunction) sig_disconnect, rec); + + if (timeout_tag == -1) { + timeout_tag = g_timeout_add(10000, (GSourceFunc) + sig_timeout_disconnect, NULL); + } + + disconnects = g_slist_append(disconnects, rec); +} + +void net_disconnect_init(void) +{ + disconnects = NULL; + timeout_tag = -1; +} + +void net_disconnect_deinit(void) +{ +#ifndef WIN32 + NET_DISCONNECT_REC *rec; + time_t now, max; + int first, fd; + struct timeval tv; + fd_set set; + + /* give the sockets a chance to disconnect themselves.. */ + max = time(NULL)+MAX_QUIT_CLOSE_WAIT; + first = 1; + while (disconnects != NULL) { + rec = disconnects->data; + + now = time(NULL); + if (rec->created+MAX_QUIT_CLOSE_WAIT <= now || max <= now) { + /* this one has waited enough */ + net_disconnect_remove(rec); + continue; + } + + fd = g_io_channel_unix_get_fd(rec->handle); + FD_ZERO(&set); + FD_SET(fd, &set); + tv.tv_sec = first ? 0 : max-now; + tv.tv_usec = first ? 100000 : 0; + if (select(fd+1, &set, NULL, NULL, &tv) > 0 && + FD_ISSET(fd, &set)) { + /* data coming .. check if we can close the handle */ + sig_disconnect(rec); + } else if (first) { + /* Display the text when we have already waited + for a while */ + printf("Please wait, waiting for servers to close " + "connections..\n"); + fflush(stdout); + + first = 0; + } + } +#endif +} diff --git a/apps/irssi/src/core/net-disconnect.h b/apps/irssi/src/core/net-disconnect.h new file mode 100644 index 00000000..a1ca0643 --- /dev/null +++ b/apps/irssi/src/core/net-disconnect.h @@ -0,0 +1,7 @@ +#ifndef __NET_DISCONNECT_H +#define __NET_DISCONNECT_H + +void net_disconnect_init(void); +void net_disconnect_deinit(void); + +#endif diff --git a/apps/irssi/src/core/net-nonblock.c b/apps/irssi/src/core/net-nonblock.c new file mode 100644 index 00000000..7392f429 --- /dev/null +++ b/apps/irssi/src/core/net-nonblock.c @@ -0,0 +1,252 @@ +/* + net-nonblock.c : Nonblocking net_connect() + + Copyright (C) 1998-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include + +#include "pidwait.h" +#include "net-nonblock.h" + +typedef struct { + NET_CALLBACK func; + void *data; + + GIOChannel *pipes[2]; + int port; + IPADDR *my_ip; + int tag; +} SIMPLE_THREAD_REC; + +#define is_fatal_error(err) \ + (err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR) + +static int g_io_channel_write_block(GIOChannel *channel, void *data, int len) +{ + unsigned int ret; + int err, sent; + + sent = 0; + do { + err = g_io_channel_write(channel, (char *) data + sent, + len-sent, &ret); + sent += ret; + } while (sent < len && !is_fatal_error(err)); + + return err != 0 ? -1 : 0; +} + +static int g_io_channel_read_block(GIOChannel *channel, void *data, int len) +{ + time_t maxwait; + unsigned int ret; + int err, received; + + maxwait = time(NULL)+2; + received = 0; + do { + err = g_io_channel_read(channel, (char *) data + received, + len-received, &ret); + received += ret; + } while (received < len && time(NULL) < maxwait && + (ret != 0 || !is_fatal_error(err))); + + return received < len ? -1 : 0; +} + +/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is + written to pipe when found PID of the resolver child is returned */ +int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe) +{ + RESOLVED_IP_REC rec; + const char *errorstr; +#ifndef WIN32 + int pid; +#endif + + g_return_val_if_fail(addr != NULL, FALSE); + +#ifndef WIN32 + pid = fork(); + if (pid > 0) { + /* parent */ + pidwait_add(pid); + return pid; + } + + if (pid != 0) { + /* failed! */ + g_warning("net_connect_thread(): fork() failed! " + "Using blocking resolving"); + } +#endif + + /* child */ + memset(&rec, 0, sizeof(rec)); + rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6); + if (rec.error == 0) { + errorstr = NULL; + } else { + errorstr = net_gethosterror(rec.error); + rec.errlen = errorstr == NULL ? 0 : strlen(errorstr)+1; + } + + g_io_channel_write_block(pipe, &rec, sizeof(rec)); + if (rec.errlen != 0) + g_io_channel_write_block(pipe, (void *) errorstr, rec.errlen); + +#ifndef WIN32 + if (pid == 0) + _exit(99); +#endif + + /* we used blocking lookup */ + return 0; +} + +/* get the resolved IP address */ +int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec) +{ + rec->error = -1; + rec->errorstr = NULL; + +#ifndef WIN32 + fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK); +#endif + + /* get ip+error */ + if (g_io_channel_read_block(pipe, rec, sizeof(*rec)) == -1) { + rec->errorstr = g_strdup_printf("Host name lookup: %s", + g_strerror(errno)); + return -1; + } + + if (rec->error) { + /* read error string, if we can't read everything for some + reason, just ignore it. */ + rec->errorstr = g_malloc0(rec->errlen+1); + g_io_channel_read_block(pipe, rec->errorstr, rec->errlen); + } + + return 0; +} + +/* Get host name, call func when finished */ +int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data) +{ + /* FIXME: not implemented */ + return FALSE; +} + +/* Kill the resolver child */ +void net_disconnect_nonblock(int pid) +{ + g_return_if_fail(pid > 0); + +#ifndef WIN32 + kill(pid, SIGKILL); +#endif +} + +static void simple_init(SIMPLE_THREAD_REC *rec, GIOChannel *handle) +{ + g_return_if_fail(rec != NULL); + + g_source_remove(rec->tag); + + if (net_geterror(handle) != 0) { + /* failed */ + g_io_channel_close(handle); + g_io_channel_unref(handle); + handle = NULL; + } + + rec->func(handle, rec->data); + g_free(rec); +} + +static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe) +{ + RESOLVED_IP_REC iprec; + GIOChannel *handle; + IPADDR *ip; + + g_return_if_fail(rec != NULL); + + g_source_remove(rec->tag); + + net_gethostbyname_return(pipe, &iprec); + g_free_not_null(iprec.errorstr); + + g_io_channel_close(rec->pipes[0]); + g_io_channel_unref(rec->pipes[0]); + g_io_channel_close(rec->pipes[1]); + g_io_channel_unref(rec->pipes[1]); + + ip = iprec.ip4.family != 0 ? &iprec.ip4 : &iprec.ip6; + handle = iprec.error == -1 ? NULL : + net_connect_ip(ip, rec->port, rec->my_ip); + + g_free_not_null(rec->my_ip); + + if (handle == NULL) { + /* failed */ + rec->func(NULL, rec->data); + g_free(rec); + return; + } + + rec->tag = g_input_add(handle, G_INPUT_READ | G_INPUT_WRITE, + (GInputFunction) simple_init, rec); +} + +/* Connect to server, call func when finished */ +int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip, + NET_CALLBACK func, void *data) +{ + SIMPLE_THREAD_REC *rec; + int fd[2]; + + g_return_val_if_fail(server != NULL, FALSE); + g_return_val_if_fail(func != NULL, FALSE); + + if (pipe(fd) != 0) { + g_warning("net_connect_nonblock(): pipe() failed."); + return FALSE; + } + + rec = g_new0(SIMPLE_THREAD_REC, 1); + rec->port = port; + if (my_ip != NULL) { + rec->my_ip = g_malloc(sizeof(IPADDR)); + memcpy(rec->my_ip, my_ip, sizeof(IPADDR)); + } + rec->func = func; + rec->data = data; + rec->pipes[0] = g_io_channel_unix_new(fd[0]); + rec->pipes[1] = g_io_channel_unix_new(fd[1]); + + /* start nonblocking host name lookup */ + net_gethostbyname_nonblock(server, rec->pipes[1]); + rec->tag = g_input_add(rec->pipes[0], G_INPUT_READ, + (GInputFunction) simple_readpipe, rec); + + return TRUE; +} diff --git a/apps/irssi/src/core/net-nonblock.h b/apps/irssi/src/core/net-nonblock.h new file mode 100644 index 00000000..a0e5cddf --- /dev/null +++ b/apps/irssi/src/core/net-nonblock.h @@ -0,0 +1,39 @@ +#ifndef __NET_NONBLOCK_H +#define __NET_NONBLOCK_H + +#include "network.h" + +typedef struct { + IPADDR ip4, ip6; /* resolved ip addresses */ + int error; /* error, 0 = no error, -1 = error: */ + int errlen; /* error text length */ + char *errorstr; /* error string - dynamically allocated, you'll + need to free() it yourself unless it's NULL */ +} RESOLVED_IP_REC; + +typedef struct { + int namelen; + char *name; + + int error; + int errlen; + char *errorstr; +} RESOLVED_NAME_REC; + +typedef void (*NET_CALLBACK) (GIOChannel *, void *); +typedef void (*NET_HOST_CALLBACK) (RESOLVED_NAME_REC *, void *); + +/* nonblocking gethostbyname(), PID of the resolver child is returned. */ +int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe); +/* Get host's name, call func when finished */ +int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data); +/* get the resolved IP address. returns -1 if some error occured with read() */ +int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec); + +/* Connect to server, call func when finished */ +int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip, + NET_CALLBACK func, void *data); +/* Kill the resolver child */ +void net_disconnect_nonblock(int pid); + +#endif diff --git a/apps/irssi/src/core/net-sendbuffer.c b/apps/irssi/src/core/net-sendbuffer.c new file mode 100644 index 00000000..04eab80a --- /dev/null +++ b/apps/irssi/src/core/net-sendbuffer.c @@ -0,0 +1,158 @@ +/* + net-sendbuffer.c : Buffered send() + + Copyright (C) 1998-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "network.h" +#include "net-sendbuffer.h" + +struct _NET_SENDBUF_REC { + GIOChannel *handle; + + int send_tag; + int bufsize; + int bufpos; + char *buffer; /* Buffer is NULL until it's actually needed. */ +}; + +static GSList *buffers; + +/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE + is used */ +NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize) +{ + NET_SENDBUF_REC *rec; + + g_return_val_if_fail(handle != NULL, NULL); + + rec = g_new0(NET_SENDBUF_REC, 1); + rec->send_tag = -1; + rec->handle = handle; + rec->bufsize = bufsize > 0 ? bufsize : DEFAULT_BUFFER_SIZE; + + buffers = g_slist_append(buffers, rec); + return rec; +} + +/* Destroy the buffer. `close' specifies if socket handle should be closed. */ +void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close) +{ + buffers = g_slist_remove(buffers, rec); + + if (rec->send_tag != -1) g_source_remove(rec->send_tag); + if (close) net_disconnect(rec->handle); + g_free_not_null(rec->buffer); + g_free(rec); +} + +/* Transmit all data from buffer - return TRUE if successful */ +static int buffer_send(NET_SENDBUF_REC *rec) +{ + int ret; + + ret = net_transmit(rec->handle, rec->buffer, rec->bufpos); + if (ret < 0 || rec->bufpos == ret) { + /* error/all sent - don't try to send it anymore */ + g_free_and_null(rec->buffer); + return TRUE; + } + + if (ret > 0) { + rec->bufpos -= ret; + g_memmove(rec->buffer, rec->buffer+ret, rec->bufpos); + } + return FALSE; +} + +static void sig_sendbuffer(NET_SENDBUF_REC *rec) +{ + if (rec->buffer != NULL) { + if (!buffer_send(rec)) + return; + } + + g_source_remove(rec->send_tag); + rec->send_tag = -1; +} + +/* Add `data' to transmit buffer - return FALSE if buffer is full */ +static int buffer_add(NET_SENDBUF_REC *rec, const void *data, int size) +{ + if (rec->buffer == NULL) { + rec->buffer = g_malloc(rec->bufsize); + rec->bufpos = 0; + } + + if (rec->bufpos+size > rec->bufsize) + return FALSE; + + memcpy(rec->buffer+rec->bufpos, data, size); + rec->bufpos += size; + return TRUE; +} + +/* Send data, if all of it couldn't be sent immediately, it will be resent + automatically after a while. Returns -1 if some unrecoverable error + occured. */ +int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size) +{ + int ret; + + g_return_val_if_fail(rec != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + if (size <= 0) return 0; + + if (rec->buffer == NULL) { + /* nothing in buffer - transmit immediately */ + ret = net_transmit(rec->handle, data, size); + if (ret < 0) return -1; + size -= ret; + data = ((const char *) data) + ret; + } + + if (size <= 0) + return 0; + + /* everything couldn't be sent. */ + if (rec->send_tag == -1) { + rec->send_tag = + g_input_add(rec->handle, G_INPUT_WRITE, + (GInputFunction) sig_sendbuffer, rec); + } + + return buffer_add(rec, data, size) ? 0 : -1; +} + +/* Returns the socket handle */ +GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec) +{ + g_return_val_if_fail(rec != NULL, NULL); + + return rec->handle; +} + +void net_sendbuffer_init(void) +{ + buffers = NULL; +} + +void net_sendbuffer_deinit(void) +{ +} diff --git a/apps/irssi/src/core/net-sendbuffer.h b/apps/irssi/src/core/net-sendbuffer.h new file mode 100644 index 00000000..bb6d8e07 --- /dev/null +++ b/apps/irssi/src/core/net-sendbuffer.h @@ -0,0 +1,23 @@ +#ifndef __NET_SENDBUFFER_H +#define __NET_SENDBUFFER_H + +#define DEFAULT_BUFFER_SIZE 8192 + +/* Create new buffer - if `bufsize' is zero or less, DEFAULT_BUFFER_SIZE + is used */ +NET_SENDBUF_REC *net_sendbuffer_create(GIOChannel *handle, int bufsize); +/* Destroy the buffer. `close' specifies if socket handle should be closed. */ +void net_sendbuffer_destroy(NET_SENDBUF_REC *rec, int close); + +/* Send data, if all of it couldn't be sent immediately, it will be resent + automatically after a while. Returns -1 if some unrecoverable error + occured. */ +int net_sendbuffer_send(NET_SENDBUF_REC *rec, const void *data, int size); + +/* Returns the socket handle */ +GIOChannel *net_sendbuffer_handle(NET_SENDBUF_REC *rec); + +void net_sendbuffer_init(void); +void net_sendbuffer_deinit(void); + +#endif diff --git a/apps/irssi/src/core/network.c b/apps/irssi/src/core/network.c new file mode 100644 index 00000000..4fc06c05 --- /dev/null +++ b/apps/irssi/src/core/network.c @@ -0,0 +1,597 @@ +/* + network.c : Network stuff + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "network.h" + +#ifndef INADDR_NONE +# define INADDR_NONE INADDR_BROADCAST +#endif + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef HAVE_IPV6 + struct sockaddr_in6 sin6; +#endif +}; + +#ifdef HAVE_IPV6 +# define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \ + sizeof(so.sin6) : sizeof(so.sin)) +#else +# define SIZEOF_SOCKADDR(so) (sizeof(so.sin)) +#endif + +#ifdef WIN32 +# define g_io_channel_new(handle) g_io_channel_win32_new_stream_socket(handle) +#else +# define g_io_channel_new(handle) g_io_channel_unix_new(handle) +#endif + +/* Cygwin need this, don't know others.. */ +/*#define BLOCKING_SOCKETS 1*/ + +int net_ip_compare(IPADDR *ip1, IPADDR *ip2) +{ + if (ip1->family != ip2->family) + return 0; + +#ifdef HAVE_IPV6 + if (ip1->family == AF_INET6) + return memcmp(&ip1->ip, &ip2->ip, sizeof(ip1->ip)) == 0; +#endif + + return memcmp(&ip1->ip, &ip2->ip, 4) == 0; +} + + +/* copy IP to sockaddr */ +#ifdef G_CAN_INLINE +G_INLINE_FUNC +#else +static +#endif +void sin_set_ip(union sockaddr_union *so, const IPADDR *ip) +{ + if (ip == NULL) { +#ifdef HAVE_IPV6 + so->sin6.sin6_family = AF_INET6; + so->sin6.sin6_addr = in6addr_any; +#else + so->sin.sin_family = AF_INET; + so->sin.sin_addr.s_addr = INADDR_ANY; +#endif + return; + } + + so->sin.sin_family = ip->family; +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&so->sin6.sin6_addr, &ip->ip, sizeof(ip->ip)); + else +#endif + memcpy(&so->sin.sin_addr, &ip->ip, 4); +} + +void sin_get_ip(const union sockaddr_union *so, IPADDR *ip) +{ + ip->family = so->sin.sin_family; + +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&ip->ip, &so->sin6.sin6_addr, sizeof(ip->ip)); + else +#endif + memcpy(&ip->ip, &so->sin.sin_addr, 4); +} + +#ifdef G_CAN_INLINE +G_INLINE_FUNC +#else +static +#endif +void sin_set_port(union sockaddr_union *so, int port) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + so->sin6.sin6_port = htons(port); + else +#endif + so->sin.sin_port = htons((unsigned short)port); +} + +#ifdef G_CAN_INLINE +G_INLINE_FUNC +#else +static +#endif +int sin_get_port(union sockaddr_union *so) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + return ntohs(so->sin6.sin6_port); +#endif + return ntohs(so->sin.sin_port); +} + +/* Connect to socket */ +GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip) +{ + IPADDR ip4, ip6, *ip; + int family; + + g_return_val_if_fail(addr != NULL, NULL); + + family = my_ip == NULL ? 0 : my_ip->family; + if (net_gethostbyname(addr, &ip4, &ip6) == -1) + return NULL; + + if (my_ip == NULL) { + /* prefer IPv4 addresses */ + ip = ip4.family != 0 ? &ip4 : &ip6; + } else if (IPADDR_IS_V6(my_ip)) { + /* my_ip is IPv6 address, use it if possible */ + if (ip6.family != 0) + ip = &ip6; + else { + my_ip = NULL; + ip = &ip4; + } + } else { + /* my_ip is IPv4 address, use it if possible */ + if (ip4.family != 0) + ip = &ip4; + else { + my_ip = NULL; + ip = &ip6; + } + } + + return net_connect_ip(ip, port, my_ip); +} + +/* Connect to socket with ip address */ +GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip) +{ + union sockaddr_union so; + int handle, ret, opt = 1; + + if (my_ip != NULL && ip->family != my_ip->family) { + g_warning("net_connect_ip(): ip->family != my_ip->family"); + my_ip = NULL; + } + + /* create the socket */ + memset(&so, 0, sizeof(so)); + so.sin.sin_family = ip->family; + handle = socket(ip->family, SOCK_STREAM, 0); + + if (handle == -1) + return NULL; + + /* set socket options */ +#ifndef WIN32 + fcntl(handle, F_SETFL, O_NONBLOCK); +#endif + setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, + (char *) &opt, sizeof(opt)); + setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, + (char *) &opt, sizeof(opt)); + + /* set our own address, ignore if bind() fails */ + if (my_ip != NULL) { + sin_set_ip(&so, my_ip); + bind(handle, &so.sa, SIZEOF_SOCKADDR(so)); + } + + /* connect */ + sin_set_ip(&so, ip); + sin_set_port(&so, port); + ret = connect(handle, &so.sa, SIZEOF_SOCKADDR(so)); + +#ifndef WIN32 + if (ret < 0 && errno != EINPROGRESS) { +#else + if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) { +#endif + close(handle); + return NULL; + } + + return g_io_channel_new(handle); +} + +/* Disconnect socket */ +void net_disconnect(GIOChannel *handle) +{ + g_return_if_fail(handle != NULL); + + g_io_channel_close(handle); + g_io_channel_unref(handle); +} + +/* Listen for connections on a socket. if `my_ip' is NULL, listen in any + address. */ +GIOChannel *net_listen(IPADDR *my_ip, int *port) +{ + union sockaddr_union so; + int ret, handle, opt = 1; + socklen_t len; + + g_return_val_if_fail(port != NULL, NULL); + + memset(&so, 0, sizeof(so)); + sin_set_port(&so, *port); + sin_set_ip(&so, my_ip); + + /* create the socket */ + handle = socket(so.sin.sin_family, SOCK_STREAM, 0); +#ifdef HAVE_IPV6 + if (handle == -1 && errno == EINVAL) { + /* IPv6 is not supported by OS */ + so.sin.sin_family = AF_INET; + so.sin.sin_addr.s_addr = INADDR_ANY; + + handle = socket(AF_INET, SOCK_STREAM, 0); + } +#endif + if (handle == -1) + return NULL; + + /* set socket options */ +#ifndef WIN32 + fcntl(handle, F_SETFL, O_NONBLOCK); +#endif + setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, + (char *) &opt, sizeof(opt)); + setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, + (char *) &opt, sizeof(opt)); + + /* specify the address/port we want to listen in */ + ret = bind(handle, &so.sa, SIZEOF_SOCKADDR(so)); + if (ret >= 0) { + /* get the actual port we started listen */ + len = SIZEOF_SOCKADDR(so); + ret = getsockname(handle, &so.sa, &len); + if (ret >= 0) { + *port = sin_get_port(&so); + + /* start listening */ + if (listen(handle, 1) >= 0) + return g_io_channel_new(handle); + } + + } + + /* error */ + close(handle); + return NULL; +} + +/* Accept a connection on a socket */ +GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port) +{ + union sockaddr_union so; + int ret; + socklen_t addrlen; + + g_return_val_if_fail(handle != NULL, NULL); + + addrlen = sizeof(so); + ret = accept(g_io_channel_unix_get_fd(handle), &so.sa, &addrlen); + + if (ret < 0) + return NULL; + + if (addr != NULL) sin_get_ip(&so, addr); + if (port != NULL) *port = sin_get_port(&so); + +#ifndef WIN32 + fcntl(ret, F_SETFL, O_NONBLOCK); +#endif + return g_io_channel_new(ret); +} + +/* Read data from socket, return number of bytes read, -1 = error */ +int net_receive(GIOChannel *handle, char *buf, int len) +{ + unsigned int ret; + int err; + + g_return_val_if_fail(handle != NULL, -1); + g_return_val_if_fail(buf != NULL, -1); + + err = g_io_channel_read(handle, buf, len, &ret); + if (err == 0 && ret == 0) + return -1; /* disconnected */ + + if (err == G_IO_ERROR_AGAIN || (err != 0 && errno == EINTR)) + return 0; /* no bytes received */ + + return err == 0 ? (int)ret : -1; +} + +/* Transmit data, return number of bytes sent, -1 = error */ +int net_transmit(GIOChannel *handle, const char *data, int len) +{ + unsigned int ret; + int err; + + g_return_val_if_fail(handle != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + + err = g_io_channel_write(handle, (char *) data, len, &ret); + if (err == G_IO_ERROR_AGAIN || + (err != 0 && (errno == EINTR || errno == EPIPE))) + return 0; + + return err == 0 ? (int)ret : -1; +} + +/* Get socket address/port */ +int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port) +{ + union sockaddr_union so; + socklen_t addrlen; + + g_return_val_if_fail(handle != NULL, -1); + g_return_val_if_fail(addr != NULL, -1); + + addrlen = sizeof(so); + if (getsockname(g_io_channel_unix_get_fd(handle), + (struct sockaddr *) &so, &addrlen) == -1) + return -1; + + sin_get_ip(&so, addr); + if (port) *port = sin_get_port(&so); + + return 0; +} + +/* Get IP addresses for host, both IPv4 and IPv6 if possible. + If ip->family is 0, the address wasn't found. + Returns 0 = ok, others = error code for net_gethosterror() */ +int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6) +{ +#ifdef HAVE_IPV6 + union sockaddr_union *so; + struct addrinfo hints, *ai, *origai; + char hbuf[NI_MAXHOST]; + int host_error, count; +#else + struct hostent *hp; +#endif + + g_return_val_if_fail(addr != NULL, -1); + + memset(ip4, 0, sizeof(IPADDR)); + memset(ip6, 0, sizeof(IPADDR)); + +#ifdef HAVE_IPV6 + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + + /* save error to host_error for later use */ + host_error = getaddrinfo(addr, NULL, &hints, &ai); + if (host_error != 0) + return host_error; + + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST)) + return 1; + + origai = ai; count = 0; + while (ai != NULL && count < 2) { + so = (union sockaddr_union *) ai->ai_addr; + + if (ai->ai_family == AF_INET6 && ip6->family == 0) { + sin_get_ip(so, ip6); + count++; + } else if (ai->ai_family == AF_INET && ip4->family == 0) { + sin_get_ip(so, ip4); + count++; + } + ai = ai->ai_next; + } + freeaddrinfo(origai); +#else + hp = gethostbyname(addr); + if (hp == NULL) return h_errno; + + ip4->family = AF_INET; + memcpy(&ip4->ip, hp->h_addr, 4); +#endif + + return 0; +} + +/* Get name for host, *name should be g_free()'d unless it's NULL. + Return values are the same as with net_gethostbyname() */ +int net_gethostbyaddr(IPADDR *ip, char **name) +{ +#ifdef HAVE_IPV6 + struct addrinfo req, *ai; + int host_error; +#else + struct hostent *hp; +#endif + char ipname[MAX_IP_LEN]; + + g_return_val_if_fail(ip != NULL, -1); + g_return_val_if_fail(name != NULL, -1); + + net_ip2host(ip, ipname); + + *name = NULL; +#ifdef HAVE_IPV6 + memset(&req, 0, sizeof(struct addrinfo)); + req.ai_socktype = SOCK_STREAM; + req.ai_flags = AI_CANONNAME; + + /* save error to host_error for later use */ + host_error = getaddrinfo(ipname, NULL, &req, &ai); + if (host_error != 0) + return host_error; + *name = g_strdup(ai->ai_canonname); + + freeaddrinfo(ai); +#else + hp = gethostbyaddr(ipname, strlen(ipname), AF_INET); + if (hp == NULL) return -1; + + *name = g_strdup(hp->h_name); +#endif + + return 0; +} + +int net_ip2host(IPADDR *ip, char *host) +{ +#ifdef HAVE_IPV6 + if (!inet_ntop(ip->family, &ip->ip, host, MAX_IP_LEN)) + return -1; +#else + unsigned long ip4; + + ip4 = ntohl(ip->ip.s_addr); + g_snprintf(host, MAX_IP_LEN, "%lu.%lu.%lu.%lu", + (ip4 & 0xff000000UL) >> 24, + (ip4 & 0x00ff0000) >> 16, + (ip4 & 0x0000ff00) >> 8, + (ip4 & 0x000000ff)); +#endif + return 0; +} + +int net_host2ip(const char *host, IPADDR *ip) +{ + unsigned long addr; + +#ifdef HAVE_IPV6 + if (strchr(host, ':') != NULL) { + /* IPv6 */ + ip->family = AF_INET6; + if (inet_pton(AF_INET6, host, &ip->ip) == 0) + return -1; + } else +#endif + { + /* IPv4 */ + ip->family = AF_INET; +#ifdef HAVE_INET_ATON + if (inet_aton(host, &ip->ip.s_addr) == 0) + return -1; +#else + addr = inet_addr(host); + if (addr == INADDR_NONE) + return -1; + + memcpy(&ip->ip, &addr, 4); +#endif + } + + return 0; +} + +/* Get socket error */ +int net_geterror(GIOChannel *handle) +{ + int data; + socklen_t len = sizeof(data); + + if (getsockopt(g_io_channel_unix_get_fd(handle), + SOL_SOCKET, SO_ERROR, (void *) &data, &len) == -1) + return -1; + + return data; +} + +/* get error of net_gethostname() */ +const char *net_gethosterror(int error) +{ +#ifdef HAVE_IPV6 + g_return_val_if_fail(error != 0, NULL); + + if (error == 1) { + /* getnameinfo() failed .. + FIXME: does strerror return the right error message? */ + return g_strerror(errno); + } + + return gai_strerror(error); +#else + switch (error) { + case HOST_NOT_FOUND: + return "Host not found"; + case NO_ADDRESS: + return "No IP address found for name"; + case NO_RECOVERY: + return "A non-recovable name server error occurred"; + case TRY_AGAIN: + return "A temporary error on an authoritative name server"; + } + + /* unknown error */ + return NULL; +#endif +} + +/* return TRUE if host lookup failed because it didn't exist (ie. not + some error with name server) */ +int net_hosterror_notfound(int error) +{ +#ifdef HAVE_IPV6 + return error != 1 && (error == EAI_NONAME || error == EAI_NODATA); +#else + return error == HOST_NOT_FOUND || error == NO_ADDRESS; +#endif +} + +/* Get name of TCP service */ +char *net_getservbyport(int port) +{ + struct servent *entry; + + entry = getservbyport(htons((unsigned short) port), "tcp"); + return entry == NULL ? NULL : entry->s_name; +} + +int is_ipv4_address(const char *host) +{ + while (*host != '\0') { + if (*host != '.' && !isdigit(*host)) + return 0; + host++; + } + + return 1; +} + +int is_ipv6_address(const char *host) +{ + while (*host != '\0') { + if (*host != ':' && !isxdigit(*host)) + return 0; + host++; + } + + return 1; +} diff --git a/apps/irssi/src/core/network.h b/apps/irssi/src/core/network.h new file mode 100644 index 00000000..4c25740f --- /dev/null +++ b/apps/irssi/src/core/network.h @@ -0,0 +1,95 @@ +#ifndef __NETWORK_H +#define __NETWORK_H + +#ifdef HAVE_SOCKS_H +#include +#endif + +#include +#ifndef WIN32 +# include +# include +# include +# include +#endif + +#ifndef AF_INET6 +# ifdef PF_INET6 +# define AF_INET6 PF_INET6 +# else +# define AF_INET6 10 +# endif +#endif + +struct _IPADDR { + unsigned short family; +#ifdef HAVE_IPV6 + struct in6_addr ip; +#else + struct in_addr ip; +#endif +}; + +/* maxmimum string length of IP address */ +#ifdef HAVE_IPV6 +# define MAX_IP_LEN INET6_ADDRSTRLEN +#else +# define MAX_IP_LEN 20 +#endif + +#define IPADDR_IS_V6(ip) ((ip)->family != AF_INET) + +/* returns 1 if IPADDRs are the same */ +int net_ip_compare(IPADDR *ip1, IPADDR *ip2); + +/* Connect to socket */ +GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip); +/* Connect to socket with ip address */ +GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip); +/* Disconnect socket */ +void net_disconnect(GIOChannel *handle); +/* Try to let the other side close the connection, if it still isn't + disconnected after certain amount of time, close it ourself */ +void net_disconnect_later(GIOChannel *handle); + +/* Listen for connections on a socket */ +GIOChannel *net_listen(IPADDR *my_ip, int *port); +/* Accept a connection on a socket */ +GIOChannel *net_accept(GIOChannel *handle, IPADDR *addr, int *port); + +/* Read data from socket, return number of bytes read, -1 = error */ +int net_receive(GIOChannel *handle, char *buf, int len); +/* Transmit data, return number of bytes sent, -1 = error */ +int net_transmit(GIOChannel *handle, const char *data, int len); + +/* Get IP addresses for host, both IPv4 and IPv6 if possible. + If ip->family is 0, the address wasn't found. + Returns 0 = ok, others = error code for net_gethosterror() */ +int net_gethostbyname(const char *addr, IPADDR *ip4, IPADDR *ip6); +/* Get name for host, *name should be g_free()'d unless it's NULL. + Return values are the same as with net_gethostbyname() */ +int net_gethostbyaddr(IPADDR *ip, char **name); +/* get error of net_gethostname() */ +const char *net_gethosterror(int error); +/* return TRUE if host lookup failed because it didn't exist (ie. not + some error with name server) */ +int net_hosterror_notfound(int error); + +/* Get socket address/port */ +int net_getsockname(GIOChannel *handle, IPADDR *addr, int *port); + +/* IPADDR -> char* translation. `host' must be at least MAX_IP_LEN bytes */ +int net_ip2host(IPADDR *ip, char *host); +/* char* -> IPADDR translation. */ +int net_host2ip(const char *host, IPADDR *ip); + +/* Get socket error */ +int net_geterror(GIOChannel *handle); + +/* Get name of TCP service */ +char *net_getservbyport(int port); + +int is_ipv4_address(const char *host); +int is_ipv6_address(const char *host); + +#endif diff --git a/apps/irssi/src/core/nick-rec.h b/apps/irssi/src/core/nick-rec.h new file mode 100644 index 00000000..7dff6f32 --- /dev/null +++ b/apps/irssi/src/core/nick-rec.h @@ -0,0 +1,27 @@ +/* NICK_REC definition, used for inheritance */ + +int type; /* module_get_uniq_id("NICK", 0) */ +int chat_type; /* chat_protocol_lookup(xx) */ + +time_t last_check; /* last time gone was checked */ + +char *nick; +char *host; +char *realname; +int hops; + +/* status in server */ +unsigned int gone:1; +unsigned int serverop:1; + +/* status in channel */ +unsigned int send_massjoin:1; /* Waiting to be sent in massjoin signal */ +unsigned int op:1; +unsigned int halfop:1; +unsigned int voice:1; + +GHashTable *module_data; + +void *unique_id; /* unique ID to use for comparing if one nick is in another channels, + or NULL = nicks are unique, just keep comparing them. */ +NICK_REC *next; /* support for multiple identically named nicks */ diff --git a/apps/irssi/src/core/nicklist.c b/apps/irssi/src/core/nicklist.c new file mode 100644 index 00000000..f9539ff3 --- /dev/null +++ b/apps/irssi/src/core/nicklist.c @@ -0,0 +1,577 @@ +/* + nicklist.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" + +#include "servers.h" +#include "channels.h" +#include "nicklist.h" +#include "masks.h" + +#define isalnumhigh(a) \ + (isalnum(a) || (unsigned char) (a) >= 128) + +static void nick_hash_add(CHANNEL_REC *channel, NICK_REC *nick) +{ + NICK_REC *list; + + nick->next = NULL; + + list = g_hash_table_lookup(channel->nicks, nick->nick); + if (list == NULL) + g_hash_table_insert(channel->nicks, nick->nick, nick); + else { + /* multiple nicks with same name */ + while (list->next != NULL) + list = list->next; + list->next = nick; + } + + if (nick == channel->ownnick) { + /* move our own nick to beginning of the nick list.. */ + nicklist_set_own(channel, nick); + } +} + +static void nick_hash_remove(CHANNEL_REC *channel, NICK_REC *nick) +{ + NICK_REC *list; + + list = g_hash_table_lookup(channel->nicks, nick->nick); + if (list == NULL) + return; + + if (list == nick || list->next == NULL) { + g_hash_table_remove(channel->nicks, nick->nick); + if (list->next != NULL) { + g_hash_table_insert(channel->nicks, nick->next->nick, + nick->next); + } + } else { + while (list->next != nick) + list = list->next; + list->next = nick->next; + } +} + +/* Add new nick to list */ +void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick) +{ + MODULE_DATA_INIT(nick); + + nick->type = module_get_uniq_id("NICK", 0); + nick->chat_type = channel->chat_type; + + nick_hash_add(channel, nick); + signal_emit("nicklist new", 2, channel, nick); +} + +/* Set host address for nick */ +void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host) +{ + g_return_if_fail(channel != NULL); + g_return_if_fail(nick != NULL); + g_return_if_fail(host != NULL); + + g_free_not_null(nick->host); + nick->host = g_strdup(host); + + signal_emit("nicklist host changed", 2, channel, nick); +} + +static void nicklist_destroy(CHANNEL_REC *channel, NICK_REC *nick) +{ + signal_emit("nicklist remove", 2, channel, nick); + + g_free(nick->nick); + g_free_not_null(nick->realname); + g_free_not_null(nick->host); + g_free(nick); +} + +/* Remove nick from list */ +void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick) +{ + g_return_if_fail(IS_CHANNEL(channel)); + g_return_if_fail(nick != NULL); + + nick_hash_remove(channel, nick); + nicklist_destroy(channel, nick); +} + +static void nicklist_rename_list(SERVER_REC *server, void *new_nick_id, + const char *old_nick, const char *new_nick, + GSList *nicks) +{ + CHANNEL_REC *channel; + NICK_REC *nickrec; + GSList *tmp; + + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + channel = tmp->data; + nickrec = tmp->next->data; + + /* remove old nick from hash table */ + nick_hash_remove(channel, nickrec); + + if (new_nick_id != NULL) + nickrec->unique_id = new_nick_id; + + g_free(nickrec->nick); + nickrec->nick = g_strdup(new_nick); + + /* add new nick to hash table */ + nick_hash_add(channel, nickrec); + + signal_emit("nicklist changed", 3, channel, nickrec, old_nick); + } + g_slist_free(nicks); +} + +void nicklist_rename(SERVER_REC *server, const char *old_nick, + const char *new_nick) +{ + nicklist_rename_list(server, NULL, old_nick, new_nick, + nicklist_get_same(server, old_nick)); +} + +void nicklist_rename_unique(SERVER_REC *server, + void *old_nick_id, const char *old_nick, + void *new_nick_id, const char *new_nick) +{ + nicklist_rename_list(server, new_nick_id, old_nick, new_nick, + nicklist_get_same_unique(server, old_nick_id)); +} + +static NICK_REC *nicklist_find_wildcards(CHANNEL_REC *channel, + const char *mask) +{ + GSList *nicks, *tmp; + NICK_REC *nick; + + nicks = nicklist_getnicks(channel); + nick = NULL; + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + nick = tmp->data; + + if (mask_match_address(channel->server, mask, + nick->nick, nick->host)) + break; + } + g_slist_free(nicks); + return tmp == NULL ? NULL : nick; +} + +GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask) +{ + GSList *nicks, *tmp, *next; + + g_return_val_if_fail(IS_CHANNEL(channel), NULL); + g_return_val_if_fail(mask != NULL, NULL); + + nicks = nicklist_getnicks(channel); + for (tmp = nicks; tmp != NULL; tmp = next) { + NICK_REC *nick = tmp->data; + + next = tmp->next; + if (!mask_match_address(channel->server, mask, + nick->nick, nick->host)) + nicks = g_slist_remove(nicks, tmp->data); + } + + return nicks; +} + +/* Find nick */ +NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick) +{ + g_return_val_if_fail(IS_CHANNEL(channel), NULL); + g_return_val_if_fail(nick != NULL, NULL); + + return g_hash_table_lookup(channel->nicks, nick); +} + +NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick, + void *id) +{ + NICK_REC *rec; + + g_return_val_if_fail(IS_CHANNEL(channel), NULL); + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_hash_table_lookup(channel->nicks, nick); + while (rec != NULL && rec->unique_id != id) + rec = rec->next; + + return rec; +} + +/* Find nick mask, wildcards allowed */ +NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask) +{ + NICK_REC *nickrec; + char *nick, *host; + + g_return_val_if_fail(IS_CHANNEL(channel), NULL); + g_return_val_if_fail(mask != NULL, NULL); + + nick = g_strdup(mask); + host = strchr(nick, '!'); + if (host != NULL) *host++ = '\0'; + + if (strchr(nick, '*') || strchr(nick, '?')) { + g_free(nick); + return nicklist_find_wildcards(channel, mask); + } + + nickrec = g_hash_table_lookup(channel->nicks, nick); + + if (host != NULL) { + while (nickrec != NULL) { + if (nickrec->host != NULL && + match_wildcards(host, nickrec->host)) + break; /* match */ + nickrec = nickrec->next; + } + } + g_free(nick); + return nickrec; +} + +static void get_nicks_hash(gpointer key, NICK_REC *rec, GSList **list) +{ + while (rec != NULL) { + *list = g_slist_append(*list, rec); + rec = rec->next; + } +} + +/* Get list of nicks */ +GSList *nicklist_getnicks(CHANNEL_REC *channel) +{ + GSList *list; + + g_return_val_if_fail(IS_CHANNEL(channel), NULL); + + list = NULL; + g_hash_table_foreach(channel->nicks, (GHFunc) get_nicks_hash, &list); + return list; +} + +typedef struct { + CHANNEL_REC *channel; + const char *nick; + GSList *list; +} NICKLIST_GET_SAME_REC; + +static void get_nicks_same_hash(gpointer key, NICK_REC *nick, + NICKLIST_GET_SAME_REC *rec) +{ + while (nick != NULL) { + if (g_strcasecmp(nick->nick, rec->nick) == 0) { + rec->list = g_slist_append(rec->list, rec->channel); + rec->list = g_slist_append(rec->list, nick); + } + + nick = nick->next; + } +} + +GSList *nicklist_get_same(SERVER_REC *server, const char *nick) +{ + NICKLIST_GET_SAME_REC rec; + GSList *tmp; + + g_return_val_if_fail(IS_SERVER(server), NULL); + + rec.nick = nick; + rec.list = NULL; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + rec.channel = tmp->data; + g_hash_table_foreach(rec.channel->nicks, + (GHFunc) get_nicks_same_hash, &rec); + } + return rec.list; +} + +typedef struct { + CHANNEL_REC *channel; + void *id; + GSList *list; +} NICKLIST_GET_SAME_UNIQUE_REC; + +static void get_nicks_same_hash_unique(gpointer key, NICK_REC *nick, + NICKLIST_GET_SAME_UNIQUE_REC *rec) +{ + while (nick != NULL) { + if (nick->unique_id == rec->id) { + rec->list = g_slist_append(rec->list, rec->channel); + rec->list = g_slist_append(rec->list, nick); + break; + } + + nick = nick->next; + } +} + +GSList *nicklist_get_same_unique(SERVER_REC *server, void *id) +{ + NICKLIST_GET_SAME_UNIQUE_REC rec; + GSList *tmp; + + g_return_val_if_fail(IS_SERVER(server), NULL); + g_return_val_if_fail(id != NULL, NULL); + + rec.id = id; + rec.list = NULL; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + rec.channel = tmp->data; + g_hash_table_foreach(rec.channel->nicks, + (GHFunc) get_nicks_same_hash_unique, + &rec); + } + return rec.list; +} + +/* nick record comparision for sort functions */ +int nicklist_compare(NICK_REC *p1, NICK_REC *p2) +{ + if (p1 == NULL) return -1; + if (p2 == NULL) return 1; + + if (p1->op && !p2->op) return -1; + if (!p1->op && p2->op) return 1; + + if (!p1->op) { + if (p1->voice && !p2->voice) return -1; + if (!p1->voice && p2->voice) return 1; + } + + return g_strcasecmp(p1->nick, p2->nick); +} + +static void nicklist_update_flags_list(SERVER_REC *server, int gone, + int serverop, GSList *nicks) +{ + GSList *tmp; + CHANNEL_REC *channel; + NICK_REC *rec; + + g_return_if_fail(IS_SERVER(server)); + + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + channel = tmp->data; + rec = tmp->next->data; + + rec->last_check = time(NULL); + + if (gone != -1 && (int)rec->gone != gone) { + rec->gone = gone; + signal_emit("nicklist gone changed", 2, channel, rec); + } + + if (serverop != -1 && (int)rec->serverop != serverop) { + rec->serverop = serverop; + signal_emit("nicklist serverop changed", 2, channel, rec); + } + } + g_slist_free(nicks); +} + +void nicklist_update_flags(SERVER_REC *server, const char *nick, + int gone, int serverop) +{ + nicklist_update_flags_list(server, gone, serverop, + nicklist_get_same(server, nick)); +} + +void nicklist_update_flags_unique(SERVER_REC *server, void *id, + int gone, int serverop) +{ + nicklist_update_flags_list(server, gone, serverop, + nicklist_get_same_unique(server, id)); +} + +/* Specify which nick in channel is ours */ +void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick) +{ + NICK_REC *first, *next; + + channel->ownnick = nick; + + /* move our nick in the list to first, makes some things easier + (like handling multiple identical nicks in fe-messages.c) */ + first = g_hash_table_lookup(channel->nicks, nick->nick); + if (first->next == NULL) + return; + + next = nick->next; + nick->next = first; + + while (first->next != nick) + first = first->next; + first->next = next; + + g_hash_table_insert(channel->nicks, nick->nick, nick); +} + +static void sig_channel_created(CHANNEL_REC *channel) +{ + g_return_if_fail(IS_CHANNEL(channel)); + + channel->nicks = g_hash_table_new((GHashFunc) g_istr_hash, + (GCompareFunc) g_istr_equal); +} + +static void nicklist_remove_hash(gpointer key, NICK_REC *nick, + CHANNEL_REC *channel) +{ + NICK_REC *next; + + while (nick != NULL) { + next = nick->next; + nicklist_destroy(channel, nick); + nick = next; + } +} + +static void sig_channel_destroyed(CHANNEL_REC *channel) +{ + g_return_if_fail(IS_CHANNEL(channel)); + + g_hash_table_foreach(channel->nicks, + (GHFunc) nicklist_remove_hash, channel); + g_hash_table_destroy(channel->nicks); +} + +static NICK_REC *nick_nfind(CHANNEL_REC *channel, const char *nick, int len) +{ + NICK_REC *rec; + char *tmpnick; + + tmpnick = g_strndup(nick, len); + rec = g_hash_table_lookup(channel->nicks, tmpnick); + + if (rec != NULL) { + /* if there's multiple, get the one with identical case */ + while (rec->next != NULL) { + if (strcmp(rec->nick, tmpnick) == 0) + break; + rec = rec->next; + } + } + + g_free(tmpnick); + return rec; +} + +/* Check is `msg' is meant for `nick'. */ +int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick) +{ + const char *msgstart, *orignick; + int len, fullmatch; + + g_return_val_if_fail(nick != NULL, FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + if (channel != NULL && channel->server->nick_match_msg != NULL) + return channel->server->nick_match_msg(msg, nick); + + /* first check for identical match */ + len = strlen(nick); + if (g_strncasecmp(msg, nick, len) == 0 && !isalnumhigh((int) msg[len])) + return TRUE; + + orignick = nick; + for (;;) { + nick = orignick; + msgstart = msg; + fullmatch = TRUE; + + /* check if it matches for alphanumeric parts of nick */ + while (*nick != '\0' && *msg != '\0') { + if (toupper(*nick) == toupper(*msg)) { + /* total match */ + msg++; + } else if (isalnum(*msg) && !isalnum(*nick)) { + /* some strange char in your nick, pass it */ + fullmatch = FALSE; + } else + break; + + nick++; + } + + if (msg != msgstart && !isalnumhigh(*msg)) { + /* at least some of the chars in line matched the + nick, and msg continue with non-alphanum character, + this might be for us.. */ + if (*nick != '\0') { + /* remove the rest of the non-alphanum chars + from nick and check if it then matches. */ + fullmatch = FALSE; + while (*nick != '\0' && !isalnum(*nick)) + nick++; + } + + if (*nick == '\0') { + /* yes, match! */ + break; + } + } + + /* no match. check if this is a message to multiple people + (like nick1,nick2: text) */ + while (*msg != '\0' && *msg != ' ' && *msg != ',') msg++; + + if (*msg != ',') { + nick = orignick; + break; + } + + msg++; + } + + if (*nick != '\0') + return FALSE; /* didn't match */ + + if (fullmatch) + return TRUE; /* matched without fuzzyness */ + + /* matched with some fuzzyness .. check if there's an exact match + for some other nick in the same channel. */ + return nick_nfind(channel, msgstart, (int) (msg-msgstart)) == NULL; +} + +void nicklist_init(void) +{ + signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); +} + +void nicklist_deinit(void) +{ + signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + + module_uniq_destroy("NICK"); +} diff --git a/apps/irssi/src/core/nicklist.h b/apps/irssi/src/core/nicklist.h new file mode 100644 index 00000000..a9683500 --- /dev/null +++ b/apps/irssi/src/core/nicklist.h @@ -0,0 +1,60 @@ +#ifndef __NICKLIST_H +#define __NICKLIST_H + +/* Returns NICK_REC if it's nick, NULL if it isn't. */ +#define NICK(server) \ + MODULE_CHECK_CAST(server, NICK_REC, type, "NICK") + +#define IS_NICK(server) \ + (NICK(server) ? TRUE : FALSE) + +struct _NICK_REC { +#include "nick-rec.h" +}; + +/* Add new nick to list */ +void nicklist_insert(CHANNEL_REC *channel, NICK_REC *nick); +/* Set host address for nick */ +void nicklist_set_host(CHANNEL_REC *channel, NICK_REC *nick, const char *host); +/* Remove nick from list */ +void nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick); +/* Change nick */ +void nicklist_rename(SERVER_REC *server, const char *old_nick, + const char *new_nick); +void nicklist_rename_unique(SERVER_REC *server, + void *old_nick_id, const char *old_nick, + void *new_nick_id, const char *new_nick); + +/* Find nick */ +NICK_REC *nicklist_find(CHANNEL_REC *channel, const char *nick); +NICK_REC *nicklist_find_unique(CHANNEL_REC *channel, const char *nick, + void *id); +/* Find nick mask, wildcards allowed */ +NICK_REC *nicklist_find_mask(CHANNEL_REC *channel, const char *mask); +/* Get list of nicks that match the mask */ +GSList *nicklist_find_multiple(CHANNEL_REC *channel, const char *mask); +/* Get list of nicks */ +GSList *nicklist_getnicks(CHANNEL_REC *channel); +/* Get all the nick records of `nick'. Returns channel, nick, channel, ... */ +GSList *nicklist_get_same(SERVER_REC *server, const char *nick); +GSList *nicklist_get_same_unique(SERVER_REC *server, void *id); + +/* Update specified nick's status in server. */ +void nicklist_update_flags(SERVER_REC *server, const char *nick, + int gone, int ircop); +void nicklist_update_flags_unique(SERVER_REC *server, void *id, + int gone, int ircop); + +/* Specify which nick in channel is ours */ +void nicklist_set_own(CHANNEL_REC *channel, NICK_REC *nick); + +/* Nick record comparision for sort functions */ +int nicklist_compare(NICK_REC *p1, NICK_REC *p2); + +/* Check is `msg' is meant for `nick'. */ +int nick_match_msg(CHANNEL_REC *channel, const char *msg, const char *nick); + +void nicklist_init(void); +void nicklist_deinit(void); + +#endif diff --git a/apps/irssi/src/core/nickmatch-cache.c b/apps/irssi/src/core/nickmatch-cache.c new file mode 100644 index 00000000..6605a2f4 --- /dev/null +++ b/apps/irssi/src/core/nickmatch-cache.c @@ -0,0 +1,120 @@ +/* + nickmatch-cache.c : irssi + + Copyright (C) 2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" + +#include "channels.h" +#include "nicklist.h" + +#include "nickmatch-cache.h" + +static GSList *lists; + +NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func) +{ + NICKMATCH_REC *rec; + + rec = g_new0(NICKMATCH_REC, 1); + rec->func = func; + + lists = g_slist_append(lists, rec); + return rec; +} + +void nickmatch_deinit(NICKMATCH_REC *rec) +{ + lists = g_slist_remove(lists, rec); + + g_hash_table_destroy(rec->nicks); + g_free(rec); +} + +static void nickmatch_check_channel(CHANNEL_REC *channel, NICKMATCH_REC *rec) +{ + GSList *nicks, *tmp; + + nicks = nicklist_getnicks(channel); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *nick = tmp->data; + + rec->func(rec->nicks, channel, nick); + } + g_slist_free(nicks); +} + +void nickmatch_rebuild(NICKMATCH_REC *rec) +{ + if (rec->nicks != NULL) + g_hash_table_destroy(rec->nicks); + + rec->nicks = g_hash_table_new((GHashFunc) g_direct_hash, + (GCompareFunc) g_direct_equal); + + g_slist_foreach(channels, (GFunc) nickmatch_check_channel, rec); +} + +static void sig_nick_new(CHANNEL_REC *channel, NICK_REC *nick) +{ + GSList *tmp; + + g_return_if_fail(channel != NULL); + g_return_if_fail(nick != NULL); + + for (tmp = lists; tmp != NULL; tmp = tmp->next) { + NICKMATCH_REC *rec = tmp->data; + + rec->func(rec->nicks, channel, nick); + } +} + +static void sig_nick_remove(CHANNEL_REC *channel, NICK_REC *nick) +{ + GSList *tmp; + + g_return_if_fail(channel != NULL); + g_return_if_fail(nick != NULL); + + for (tmp = lists; tmp != NULL; tmp = tmp->next) { + NICKMATCH_REC *rec = tmp->data; + + g_hash_table_remove(rec->nicks, nick); + } +} + +void nickmatch_cache_init(void) +{ + lists = NULL; + signal_add("nicklist new", (SIGNAL_FUNC) sig_nick_new); + signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_new); + signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nick_new); + signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_remove); +} + +void nickmatch_cache_deinit(void) +{ + g_slist_foreach(lists, (GFunc) nickmatch_deinit, NULL); + g_slist_free(lists); + + signal_remove("nicklist new", (SIGNAL_FUNC) sig_nick_new); + signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_new); + signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nick_new); + signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_remove); +} diff --git a/apps/irssi/src/core/nickmatch-cache.h b/apps/irssi/src/core/nickmatch-cache.h new file mode 100644 index 00000000..c4140a49 --- /dev/null +++ b/apps/irssi/src/core/nickmatch-cache.h @@ -0,0 +1,26 @@ +#ifndef __NICKMATCH_CACHE_H +#define __NICKMATCH_CACHE_H + +typedef void (*NICKMATCH_REBUILD_FUNC) (GHashTable *list, + CHANNEL_REC *channel, NICK_REC *nick); + +typedef struct { + GHashTable *nicks; + NICKMATCH_REBUILD_FUNC func; +} NICKMATCH_REC; + +NICKMATCH_REC *nickmatch_init(NICKMATCH_REBUILD_FUNC func); +void nickmatch_deinit(NICKMATCH_REC *rec); + +/* Calls rebuild function for all nicks in all channels. + This must be called soon after nickmatch_init(), before any nicklist + signals get sent. */ +void nickmatch_rebuild(NICKMATCH_REC *rec); + +#define nickmatch_find(rec, nick) \ + g_hash_table_lookup((rec)->nicks, nick) + +void nickmatch_cache_init(void); +void nickmatch_cache_deinit(void); + +#endif diff --git a/apps/irssi/src/core/pidwait.c b/apps/irssi/src/core/pidwait.c new file mode 100644 index 00000000..f54aa34d --- /dev/null +++ b/apps/irssi/src/core/pidwait.c @@ -0,0 +1,77 @@ +/* + pidwait.c : + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "modules.h" + +#include + +static GSList *pids; + +static unsigned int childcheck_tag; +static int signal_pidwait; + +/* add a pid to wait list */ +void pidwait_add(int pid) +{ + pids = g_slist_append(pids, GINT_TO_POINTER(pid)); +} + +/* remove pid from wait list */ +void pidwait_remove(int pid) +{ + pids = g_slist_remove(pids, GINT_TO_POINTER(pid)); +} + +static int child_check(void) +{ + GSList *tmp, *next; + int status; + + /* wait for each pid.. */ + for (tmp = pids; tmp != NULL; tmp = next) { + int pid = GPOINTER_TO_INT(tmp->data); + + next = tmp->next; + if (waitpid(pid, &status, WNOHANG) > 0) { + /* process terminated, remove from list */ + signal_emit_id(signal_pidwait, 2, tmp->data, + GINT_TO_POINTER(status)); + pids = g_slist_remove(pids, tmp->data); + } + } + return 1; +} + +void pidwait_init(void) +{ + pids = NULL; + childcheck_tag = g_timeout_add(1000, (GSourceFunc) child_check, NULL); + + signal_pidwait = signal_get_uniq_id("pidwait"); +} + +void pidwait_deinit(void) +{ + g_slist_free(pids); + + g_source_remove(childcheck_tag); +} diff --git a/apps/irssi/src/core/pidwait.h b/apps/irssi/src/core/pidwait.h new file mode 100644 index 00000000..3f6b84cd --- /dev/null +++ b/apps/irssi/src/core/pidwait.h @@ -0,0 +1,12 @@ +#ifndef __PIDWAIT_H +#define __PIDWAIT_H + +void pidwait_init(void); +void pidwait_deinit(void); + +/* add a pid to wait list */ +void pidwait_add(int pid); +/* remove pid from wait list */ +void pidwait_remove(int pid); + +#endif diff --git a/apps/irssi/src/core/queries.c b/apps/irssi/src/core/queries.c new file mode 100644 index 00000000..d1c51352 --- /dev/null +++ b/apps/irssi/src/core/queries.c @@ -0,0 +1,156 @@ +/* + queries.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" + +#include "servers.h" +#include "queries.h" + +GSList *queries; + +void query_init(QUERY_REC *query, int automatic) +{ + g_return_if_fail(query != NULL); + g_return_if_fail(query->name != NULL); + + queries = g_slist_append(queries, query); + + MODULE_DATA_INIT(query); + query->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "QUERY"); + if (query->server_tag != NULL) { + query->server = server_find_tag(query->server_tag); + if (query->server != NULL) { + query->server->queries = + g_slist_append(query->server->queries, query); + } + } + + signal_emit("query created", 2, query, GINT_TO_POINTER(automatic)); +} + +void query_destroy(QUERY_REC *query) +{ + g_return_if_fail(IS_QUERY(query)); + + if (query->destroying) return; + query->destroying = TRUE; + + queries = g_slist_remove(queries, query); + if (query->server != NULL) { + query->server->queries = + g_slist_remove(query->server->queries, query); + } + signal_emit("query destroyed", 1, query); + + MODULE_DATA_DEINIT(query); + g_free_not_null(query->hilight_color); + g_free_not_null(query->server_tag); + g_free_not_null(query->address); + g_free(query->name); + g_free(query); +} + +static QUERY_REC *query_find_server(SERVER_REC *server, const char *nick) +{ + GSList *tmp; + + g_return_val_if_fail(IS_SERVER(server), NULL); + + if (server->query_find_func != NULL) { + /* use the server specific query find function */ + return server->query_find_func(server, nick); + } + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, nick) == 0) + return rec; + } + + return NULL; +} + +QUERY_REC *query_find(SERVER_REC *server, const char *nick) +{ + GSList *tmp; + + g_return_val_if_fail(server == NULL || IS_SERVER(server), NULL); + g_return_val_if_fail(nick != NULL, NULL); + + if (server != NULL) + return query_find_server(server, nick); + + for (tmp = queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, nick) == 0) + return rec; + } + + return NULL; +} + +void query_change_nick(QUERY_REC *query, const char *nick) +{ + char *oldnick; + + g_return_if_fail(IS_QUERY(query)); + + oldnick = query->name; + query->name = g_strdup(nick); + signal_emit("query nick changed", 2, query, oldnick); + g_free(oldnick); +} + +void query_change_address(QUERY_REC *query, const char *address) +{ + g_return_if_fail(IS_QUERY(query)); + + g_free_not_null(query->address); + query->address = g_strdup(address); + signal_emit("query address changed", 1, query); +} + +void query_change_server(QUERY_REC *query, SERVER_REC *server) +{ + g_return_if_fail(IS_QUERY(query)); + + if (query->server != NULL) { + query->server->queries = + g_slist_remove(query->server->queries, query); + } + if (server != NULL) + server->queries = g_slist_append(server->queries, query); + + query->server = server; + signal_emit("query server changed", 1, query); +} + +void queries_init(void) +{ +} + +void queries_deinit(void) +{ + module_uniq_destroy("QUERY"); +} diff --git a/apps/irssi/src/core/queries.h b/apps/irssi/src/core/queries.h new file mode 100644 index 00000000..77ef9c37 --- /dev/null +++ b/apps/irssi/src/core/queries.h @@ -0,0 +1,34 @@ +#ifndef __QUERIES_H +#define __QUERIES_H + +#include "modules.h" + +/* Returns QUERY_REC if it's query, NULL if it isn't. */ +#define QUERY(query) \ + MODULE_CHECK_CAST_MODULE(query, QUERY_REC, type, \ + "WINDOW ITEM TYPE", "QUERY") + +#define IS_QUERY(query) \ + (QUERY(query) ? TRUE : FALSE) + +#define STRUCT_SERVER_REC SERVER_REC +struct _QUERY_REC { +#include "query-rec.h" +}; + +extern GSList *queries; + +void query_init(QUERY_REC *query, int automatic); +void query_destroy(QUERY_REC *query); + +/* Find query by name, if `server' is NULL, search from all servers */ +QUERY_REC *query_find(SERVER_REC *server, const char *nick); + +void query_change_nick(QUERY_REC *query, const char *nick); +void query_change_address(QUERY_REC *query, const char *address); +void query_change_server(QUERY_REC *query, SERVER_REC *server); + +void queries_init(void); +void queries_deinit(void); + +#endif diff --git a/apps/irssi/src/core/query-rec.h b/apps/irssi/src/core/query-rec.h new file mode 100644 index 00000000..12ef491c --- /dev/null +++ b/apps/irssi/src/core/query-rec.h @@ -0,0 +1,9 @@ +/* QUERY_REC definition, used for inheritance */ + +#include "window-item-rec.h" + +char *address; +char *server_tag; +unsigned int unwanted:1; /* TRUE if the other side closed or + some error occured (DCC chats!) */ +unsigned int destroying:1; diff --git a/apps/irssi/src/core/rawlog.c b/apps/irssi/src/core/rawlog.c new file mode 100644 index 00000000..4e47040c --- /dev/null +++ b/apps/irssi/src/core/rawlog.c @@ -0,0 +1,174 @@ +/* + rawlog.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "rawlog.h" +#include "modules.h" +#include "signals.h" +#include "misc.h" +#include "write-buffer.h" +#include "settings.h" + +static int rawlog_lines; +static int signal_rawlog; +static int log_file_create_mode; + +RAWLOG_REC *rawlog_create(void) +{ + RAWLOG_REC *rec; + + rec = g_new0(RAWLOG_REC, 1); + return rec; +} + +void rawlog_destroy(RAWLOG_REC *rawlog) +{ + g_return_if_fail(rawlog != NULL); + + g_slist_foreach(rawlog->lines, (GFunc) g_free, NULL); + g_slist_free(rawlog->lines); + + if (rawlog->logging) { + write_buffer_flush(); + close(rawlog->handle); + } + g_free(rawlog); +} + +/* NOTE! str must be dynamically allocated and must not be freed after! */ +static void rawlog_add(RAWLOG_REC *rawlog, char *str) +{ + if (rawlog->nlines < rawlog_lines || rawlog_lines <= 2) + rawlog->nlines++; + else { + g_free(rawlog->lines->data); + rawlog->lines = g_slist_remove(rawlog->lines, + rawlog->lines->data); + } + + if (rawlog->logging) { + write_buffer(rawlog->handle, str, strlen(str)); + write_buffer(rawlog->handle, "\n", 1); + } + + rawlog->lines = g_slist_append(rawlog->lines, str); + signal_emit_id(signal_rawlog, 2, rawlog, str); +} + +void rawlog_input(RAWLOG_REC *rawlog, const char *str) +{ + g_return_if_fail(rawlog != NULL); + g_return_if_fail(str != NULL); + + rawlog_add(rawlog, g_strdup_printf(">> %s", str)); +} + +void rawlog_output(RAWLOG_REC *rawlog, const char *str) +{ + g_return_if_fail(rawlog != NULL); + g_return_if_fail(str != NULL); + + rawlog_add(rawlog, g_strdup_printf("<< %s", str)); +} + +void rawlog_redirect(RAWLOG_REC *rawlog, const char *str) +{ + g_return_if_fail(rawlog != NULL); + g_return_if_fail(str != NULL); + + rawlog_add(rawlog, g_strdup_printf("--> %s", str)); +} + +static void rawlog_dump(RAWLOG_REC *rawlog, int f) +{ + GSList *tmp; + + for (tmp = rawlog->lines; tmp != NULL; tmp = tmp->next) { + write(f, tmp->data, strlen((char *) tmp->data)); + write(f, "\n", 1); + } +} + +void rawlog_open(RAWLOG_REC *rawlog, const char *fname) +{ + char *path; + + g_return_if_fail(rawlog != NULL); + g_return_if_fail(fname != NULL); + + if (rawlog->logging) + return; + + path = convert_home(fname); + rawlog->handle = open(path, O_WRONLY | O_APPEND | O_CREAT, + log_file_create_mode); + g_free(path); + + rawlog_dump(rawlog, rawlog->handle); + rawlog->logging = rawlog->handle != -1; +} + +void rawlog_close(RAWLOG_REC *rawlog) +{ + if (rawlog->logging) { + write_buffer_flush(); + close(rawlog->handle); + rawlog->logging = 0; + } +} + +void rawlog_save(RAWLOG_REC *rawlog, const char *fname) +{ + char *path; + int f; + + path = convert_home(fname); + f = open(path, O_WRONLY | O_APPEND | O_CREAT, log_file_create_mode); + g_free(path); + + rawlog_dump(rawlog, f); + close(f); +} + +void rawlog_set_size(int lines) +{ + rawlog_lines = lines; +} + +static void read_settings(void) +{ + rawlog_set_size(settings_get_int("rawlog_lines")); + log_file_create_mode = octal2dec(settings_get_int("log_create_mode")); +} + +void rawlog_init(void) +{ + signal_rawlog = signal_get_uniq_id("rawlog"); + + settings_add_int("history", "rawlog_lines", 200); + read_settings(); + + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void rawlog_deinit(void) +{ + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/core/rawlog.h b/apps/irssi/src/core/rawlog.h new file mode 100644 index 00000000..9e460a83 --- /dev/null +++ b/apps/irssi/src/core/rawlog.h @@ -0,0 +1,28 @@ +#ifndef __RAWLOG_H +#define __RAWLOG_H + +struct _RAWLOG_REC { + int logging; + int handle; + + int nlines; + GSList *lines; +}; + +RAWLOG_REC *rawlog_create(void); +void rawlog_destroy(RAWLOG_REC *rawlog); + +void rawlog_input(RAWLOG_REC *rawlog, const char *str); +void rawlog_output(RAWLOG_REC *rawlog, const char *str); +void rawlog_redirect(RAWLOG_REC *rawlog, const char *str); + +void rawlog_set_size(int lines); + +void rawlog_open(RAWLOG_REC *rawlog, const char *fname); +void rawlog_close(RAWLOG_REC *rawlog); +void rawlog_save(RAWLOG_REC *rawlog, const char *fname); + +void rawlog_init(void); +void rawlog_deinit(void); + +#endif diff --git a/apps/irssi/src/core/server-connect-rec.h b/apps/irssi/src/core/server-connect-rec.h new file mode 100644 index 00000000..f1b3d075 --- /dev/null +++ b/apps/irssi/src/core/server-connect-rec.h @@ -0,0 +1,26 @@ +/* SERVER_CONNECT_REC definition, used for inheritance */ + +int type; /* module_get_uniq_id("SERVER CONNECT", 0) */ +int chat_type; /* chat_protocol_lookup(xx) */ + +/* if we're connecting via proxy, or just NULLs */ +char *proxy; +int proxy_port; +char *proxy_string, *proxy_password; + +unsigned short family; /* 0 = don't care, AF_INET or AF_INET6 */ +char *address; +int port; +char *chatnet; + +IPADDR *own_ip4, *own_ip6; + +char *password; +char *nick; +char *username; +char *realname; + +/* when reconnecting, the old server status */ +unsigned int reconnection:1; /* we're trying to reconnect */ +char *channels; +char *away_reason; diff --git a/apps/irssi/src/core/server-rec.h b/apps/irssi/src/core/server-rec.h new file mode 100644 index 00000000..7cdc66e5 --- /dev/null +++ b/apps/irssi/src/core/server-rec.h @@ -0,0 +1,72 @@ +/* SERVER_REC definition, used for inheritance */ + +int type; /* module_get_uniq_id("SERVER", 0) */ +int chat_type; /* chat_protocol_lookup(xx) */ + +STRUCT_SERVER_CONNECT_REC *connrec; +time_t connect_time; /* connection time */ +time_t real_connect_time; /* time when server replied that we really are connected */ + +char *tag; /* tag name for addressing server */ +char *nick; /* current nick */ + +unsigned int connected:1; /* connected to server */ +unsigned int connection_lost:1; /* Connection lost unintentionally */ + +NET_SENDBUF_REC *handle; +int readtag; /* input tag */ + +/* for net_connect_nonblock() */ +GIOChannel *connect_pipe[2]; +int connect_tag; +int connect_pid; + +/* For deciding if event should be handled internally */ +GHashTable *eventtable; /* "event xxx" : GSList* of REDIRECT_RECs */ +GHashTable *eventgrouptable; /* event group : GSList* of REDIRECT_RECs */ +GHashTable *cmdtable; /* "command xxx" : REDIRECT_CMD_REC* */ + +RAWLOG_REC *rawlog; +LINEBUF_REC *buffer; /* receive buffer */ +GHashTable *module_data; + +char *version; /* server version */ +char *away_reason; +char *last_invite; /* channel where you were last invited */ +unsigned int server_operator:1; +unsigned int usermode_away:1; +unsigned int banned:1; /* not allowed to connect to this server */ +unsigned int dns_error:1; /* DNS said the host doesn't exist */ + +time_t lag_sent; /* 0 or time when last lag query was sent to server */ +time_t lag_last_check; /* last time we checked lag */ +int lag; /* server lag in milliseconds */ + +GSList *channels; +GSList *queries; + +/* -- support for multiple server types -- */ + +/* -- must not be NULL: -- */ +/* join to a number of channels, channels are specified in `data' separated + with commas. there can exist other information after first space like + channel keys etc. */ +void (*channels_join)(SERVER_REC *server, const char *data, int automatic); +/* returns true if `flag' indicates a nick flag (op/voice/halfop) */ +int (*isnickflag)(char flag); +/* returns true if `data' indicates a channel */ +int (*ischannel)(const char *data); +/* returns all nick flag characters in order op, voice, halfop. If some + of them aren't supported '\0' can be used. */ +const char *(*get_nick_flags)(void); +/* send public or private message to server */ +void (*send_message)(SERVER_REC *server, const char *target, const char *msg); + +/* -- Default implementations are used if NULL -- */ +CHANNEL_REC *(*channel_find_func)(SERVER_REC *server, const char *name); +QUERY_REC *(*query_find_func)(SERVER_REC *server, const char *nick); +int (*mask_match_func)(const char *mask, const char *data); +/* returns true if `msg' was meant for `nick' */ +int (*nick_match_msg)(const char *nick, const char *msg); + +#undef STRUCT_SERVER_CONNECT_REC diff --git a/apps/irssi/src/core/server-setup-rec.h b/apps/irssi/src/core/server-setup-rec.h new file mode 100644 index 00000000..4352bef7 --- /dev/null +++ b/apps/irssi/src/core/server-setup-rec.h @@ -0,0 +1,21 @@ +int type; /* module_get_uniq_id("SERVER SETUP", 0) */ +int chat_type; /* chat_protocol_lookup(xx) */ + +char *chatnet; + +unsigned short family; /* 0 = default, AF_INET or AF_INET6 */ +char *address; +int port; +char *password; + +char *own_host; /* address to use when connecting this server */ +IPADDR *own_ip4, *own_ip6; /* resolved own_address if not NULL */ + +time_t last_connect; /* to avoid reconnecting too fast.. */ + +unsigned int autoconnect:1; +unsigned int last_failed:1; /* if last connection attempt failed */ +unsigned int banned:1; /* if we're banned from this server */ +unsigned int dns_error:1; /* DNS said the host doesn't exist */ + +GHashTable *module_data; diff --git a/apps/irssi/src/core/servers-reconnect.c b/apps/irssi/src/core/servers-reconnect.c new file mode 100644 index 00000000..a9491f92 --- /dev/null +++ b/apps/irssi/src/core/servers-reconnect.c @@ -0,0 +1,455 @@ +/* + servers-reconnect.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "commands.h" +#include "network.h" +#include "signals.h" + +#include "chat-protocols.h" +#include "servers.h" +#include "servers-setup.h" +#include "servers-reconnect.h" + +#include "settings.h" + +GSList *reconnects; +static int last_reconnect_tag; +static int reconnect_timeout_tag; +static int reconnect_time; + +void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server) +{ + g_free_not_null(conn->away_reason); + conn->away_reason = !server->usermode_away ? NULL : + g_strdup(server->away_reason); + + signal_emit("server reconnect save status", 2, conn, server); +} + +static void server_reconnect_add(SERVER_CONNECT_REC *conn, + time_t next_connect) +{ + RECONNECT_REC *rec; + + g_return_if_fail(IS_SERVER_CONNECT(conn)); + + rec = g_new(RECONNECT_REC, 1); + rec->tag = ++last_reconnect_tag; + rec->conn = conn; + rec->next_connect = next_connect; + + reconnects = g_slist_append(reconnects, rec); +} + +void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn) +{ + g_return_if_fail(rec != NULL); + + reconnects = g_slist_remove(reconnects, rec); + + signal_emit("server reconnect remove", 1, rec); + if (free_conn) server_connect_free(rec->conn); + g_free(rec); + + if (reconnects == NULL) + last_reconnect_tag = 0; +} + +static int server_reconnect_timeout(void) +{ + SERVER_CONNECT_REC *conn; + GSList *list, *tmp; + time_t now; + + /* If server_connect() removes the next reconnection in queue, + we're screwed. I don't think this should happen anymore, but just + to be sure we don't crash, do this safely. */ + list = g_slist_copy(reconnects); + now = time(NULL); + for (tmp = list; tmp != NULL; tmp = tmp->next) { + RECONNECT_REC *rec = tmp->data; + + if (g_slist_find(reconnects, rec) == NULL) + continue; + + if (rec->next_connect <= now) { + conn = rec->conn; + server_reconnect_destroy(rec, FALSE); + CHAT_PROTOCOL(conn)->server_connect(conn); + } + } + + g_slist_free(list); + return 1; +} + +static void sserver_connect(SERVER_SETUP_REC *rec, SERVER_CONNECT_REC *conn) +{ + conn->family = rec->family; + conn->address = g_strdup(rec->address); + if (conn->port == 0) conn->port = rec->port; + + server_setup_fill_reconn(conn, rec); + if (rec->last_connect > time(NULL)-reconnect_time) { + /* can't reconnect this fast, wait.. */ + server_reconnect_add(conn, rec->last_connect+reconnect_time); + } else { + /* connect to server.. */ + CHAT_PROTOCOL(conn)->server_connect(conn); + } +} + +static SERVER_CONNECT_REC * +server_connect_copy_skeleton(SERVER_CONNECT_REC *src, int connect_info) +{ + SERVER_CONNECT_REC *dest; + + dest = NULL; + signal_emit("server connect copy", 2, &dest, src); + g_return_val_if_fail(dest != NULL, NULL); + + dest->type = module_get_uniq_id("SERVER CONNECT", 0); + dest->reconnection = src->reconnection; + dest->proxy = g_strdup(src->proxy); + dest->proxy_port = src->proxy_port; + dest->proxy_string = g_strdup(src->proxy_string); + dest->proxy_password = g_strdup(src->proxy_password); + + if (connect_info) { + dest->family = src->family; + dest->address = g_strdup(src->address); + dest->port = src->port; + dest->password = g_strdup(src->password); + } + + dest->chatnet = g_strdup(src->chatnet); + dest->nick = g_strdup(src->nick); + dest->username = g_strdup(src->username); + dest->realname = g_strdup(src->realname); + + if (src->own_ip4 != NULL) { + dest->own_ip4 = g_new(IPADDR, 1); + memcpy(dest->own_ip4, src->own_ip4, sizeof(IPADDR)); + } + if (src->own_ip6 != NULL) { + dest->own_ip6 = g_new(IPADDR, 1); + memcpy(dest->own_ip6, src->own_ip6, sizeof(IPADDR)); + } + + dest->channels = g_strdup(src->channels); + dest->away_reason = g_strdup(src->away_reason); + + return dest; +} + +#define server_should_reconnect(server) \ + ((server)->connection_lost && ((server)->connrec->chatnet != NULL || \ + (!(server)->banned && !(server)->dns_error))) + +#define sserver_connect_ok(rec, net) \ + (!(rec)->banned && !(rec)->dns_error && (rec)->chatnet != NULL && \ + g_strcasecmp((rec)->chatnet, (net)) == 0) + +static void sig_reconnect(SERVER_REC *server) +{ + SERVER_CONNECT_REC *conn; + SERVER_SETUP_REC *sserver; + GSList *tmp; + int use_next, through; + time_t now; + + g_return_if_fail(IS_SERVER(server)); + + if (reconnect_time == -1 || !server_should_reconnect(server)) + return; + + conn = server_connect_copy_skeleton(server->connrec, FALSE); + g_return_if_fail(conn != NULL); + + /* save the server status */ + if (server->connected) { + conn->reconnection = TRUE; + + reconnect_save_status(conn, server); + } + + sserver = server_setup_find(server->connrec->address, + server->connrec->port); + + if (sserver != NULL) { + /* save the last connection time/status */ + sserver->last_connect = server->connect_time == 0 ? + time(NULL) : server->connect_time; + sserver->last_failed = !server->connected; + if (server->banned) sserver->banned = TRUE; + if (server->dns_error) sserver->dns_error = TRUE; + } + + if (sserver == NULL || conn->chatnet == NULL) { + /* not in any chatnet, just reconnect back to same server */ + conn->family = server->connrec->family; + conn->address = g_strdup(server->connrec->address); + conn->port = server->connrec->port; + conn->password = g_strdup(server->connrec->password); + + if (server->connect_time != 0 && + time(NULL)-server->connect_time > reconnect_time) { + /* there's been enough time since last connection, + reconnect back immediately */ + CHAT_PROTOCOL(conn)->server_connect(conn); + } else { + /* reconnect later.. */ + server_reconnect_add(conn, (server->connect_time == 0 ? time(NULL) : + server->connect_time) + reconnect_time); + } + return; + } + + /* always try to first connect to the first on the list where we + haven't got unsuccessful connection attempts for the last half + an hour. */ + + now = time(NULL); + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SERVER_SETUP_REC *rec = tmp->data; + + if (sserver_connect_ok(rec, conn->chatnet) && + (!rec->last_connect || !rec->last_failed || + rec->last_connect < now-FAILED_RECONNECT_WAIT)) { + if (rec == sserver) + conn->port = server->connrec->port; + sserver_connect(rec, conn); + return; + } + } + + /* just try the next server in list */ + use_next = through = FALSE; + for (tmp = setupservers; tmp != NULL; ) { + SERVER_SETUP_REC *rec = tmp->data; + + if (!use_next && server->connrec->port == rec->port && + g_strcasecmp(rec->address, server->connrec->address) == 0) + use_next = TRUE; + else if (use_next && sserver_connect_ok(rec, conn->chatnet)) { + if (rec == sserver) + conn->port = server->connrec->port; + sserver_connect(rec, conn); + break; + } + + if (tmp->next != NULL) { + tmp = tmp->next; + continue; + } + + if (through) { + /* shouldn't happen unless there's no servers in + this chatnet in setup.. */ + server_connect_free(conn); + break; + } + + tmp = setupservers; + use_next = through = TRUE; + } +} + +static void sig_connected(SERVER_REC *server) +{ + g_return_if_fail(IS_SERVER(server)); + if (!server->connrec->reconnection) + return; + + if (server->connrec->channels != NULL) + server->channels_join(server, server->connrec->channels, TRUE); +} + +/* Remove all servers from reconnect list */ +/* SYNTAX: RMRECONNS */ +static void cmd_rmreconns(void) +{ + while (reconnects != NULL) + server_reconnect_destroy(reconnects->data, TRUE); +} + +static RECONNECT_REC *reconnect_find_tag(int tag) +{ + GSList *tmp; + + for (tmp = reconnects; tmp != NULL; tmp = tmp->next) { + RECONNECT_REC *rec = tmp->data; + + if (rec->tag == tag) + return rec; + } + + return NULL; +} + +static void reconnect_all(void) +{ + GSList *list; + SERVER_CONNECT_REC *conn; + RECONNECT_REC *rec; + + /* first move reconnects to another list so if server_connect() + fails and goes to reconnection list again, we won't get stuck + here forever */ + list = NULL; + while (reconnects != NULL) { + rec = reconnects->data; + + list = g_slist_append(list, rec->conn); + server_reconnect_destroy(rec, FALSE); + } + + + while (list != NULL) { + conn = list->data; + + CHAT_PROTOCOL(conn)->server_connect(conn); + list = g_slist_remove(list, conn); + } +} + +/* SYNTAX: RECONNECT */ +static void cmd_reconnect(const char *data, SERVER_REC *server) +{ + SERVER_CONNECT_REC *conn; + RECONNECT_REC *rec; + int tag; + + if (*data == '\0' && server != NULL) { + /* reconnect back to same server */ + conn = server_connect_copy_skeleton(server->connrec, TRUE); + + if (server->connected) { + reconnect_save_status(conn, server); + signal_emit("command disconnect", 2, + "* Reconnecting", server); + } + + conn->reconnection = TRUE; + CHAT_PROTOCOL(conn)->server_connect(conn); + return; + } + + if (g_strcasecmp(data, "all") == 0) { + /* reconnect all servers in reconnect queue */ + reconnect_all(); + return; + } + + if (*data == '\0') { + /* reconnect to first server in reconnection list */ + if (reconnects == NULL) + cmd_return_error(CMDERR_NOT_CONNECTED); + rec = reconnects->data; + } else { + if (g_strncasecmp(data, "RECON-", 6) == 0) + data += 6; + + tag = atoi(data); + rec = tag <= 0 ? NULL : reconnect_find_tag(tag); + + if (rec == NULL) { + signal_emit("server reconnect not found", 1, data); + return; + } + } + + conn = rec->conn; + server_reconnect_destroy(rec, FALSE); + CHAT_PROTOCOL(conn)->server_connect(conn); +} + +static void cmd_disconnect(const char *data, SERVER_REC *server) +{ + RECONNECT_REC *rec; + int tag; + + if (g_strncasecmp(data, "RECON-", 6) != 0) + return; /* handle only reconnection removing */ + + rec = sscanf(data+6, "%d", &tag) == 1 && tag > 0 ? + reconnect_find_tag(tag) : NULL; + + if (rec == NULL) + signal_emit("server reconnect not found", 1, data); + else + server_reconnect_destroy(rec, TRUE); + signal_stop(); +} + +static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto) +{ + GSList *tmp, *next; + + for (tmp = reconnects; tmp != NULL; tmp = next) { + RECONNECT_REC *rec = tmp->data; + + next = tmp->next; + if (rec->conn->chat_type == proto->id) + server_reconnect_destroy(rec, TRUE); + } +} + +static void read_settings(void) +{ + reconnect_time = settings_get_int("server_reconnect_time"); +} + +void servers_reconnect_init(void) +{ + settings_add_int("server", "server_reconnect_time", 300); + + reconnects = NULL; + last_reconnect_tag = 0; + + reconnect_timeout_tag = g_timeout_add(1000, (GSourceFunc) server_reconnect_timeout, NULL); + read_settings(); + + signal_add("server connect failed", (SIGNAL_FUNC) sig_reconnect); + signal_add("server disconnected", (SIGNAL_FUNC) sig_reconnect); + signal_add("event connected", (SIGNAL_FUNC) sig_connected); + signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + command_bind("rmreconns", NULL, (SIGNAL_FUNC) cmd_rmreconns); + command_bind("reconnect", NULL, (SIGNAL_FUNC) cmd_reconnect); + command_bind_first("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect); +} + +void servers_reconnect_deinit(void) +{ + g_source_remove(reconnect_timeout_tag); + + signal_remove("server connect failed", (SIGNAL_FUNC) sig_reconnect); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_reconnect); + signal_remove("event connected", (SIGNAL_FUNC) sig_connected); + signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + + command_unbind("rmreconns", (SIGNAL_FUNC) cmd_rmreconns); + command_unbind("reconnect", (SIGNAL_FUNC) cmd_reconnect); + command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect); +} diff --git a/apps/irssi/src/core/servers-reconnect.h b/apps/irssi/src/core/servers-reconnect.h new file mode 100644 index 00000000..b51486f6 --- /dev/null +++ b/apps/irssi/src/core/servers-reconnect.h @@ -0,0 +1,23 @@ +#ifndef __SERVER_RECONNECT_H +#define __SERVER_RECONNECT_H + +/* wait for half an hour before trying to reconnect to host where last + connection failed */ +#define FAILED_RECONNECT_WAIT (60*30) + +typedef struct { + int tag; + time_t next_connect; + + SERVER_CONNECT_REC *conn; +} RECONNECT_REC; + +extern GSList *reconnects; + +void reconnect_save_status(SERVER_CONNECT_REC *conn, SERVER_REC *server); +void server_reconnect_destroy(RECONNECT_REC *rec, int free_conn); + +void servers_reconnect_init(void); +void servers_reconnect_deinit(void); + +#endif diff --git a/apps/irssi/src/core/servers-redirect.c b/apps/irssi/src/core/servers-redirect.c new file mode 100644 index 00000000..ca340fc6 --- /dev/null +++ b/apps/irssi/src/core/servers-redirect.c @@ -0,0 +1,359 @@ +/* + server-redirect.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" + +#include "servers.h" +#include "servers-redirect.h" + +static int redirect_group; + +static void server_eventtable_destroy(char *key, GSList *value) +{ + GSList *tmp; + + g_free(key); + + for (tmp = value; tmp != NULL; tmp = tmp->next) { + REDIRECT_REC *rec = tmp->data; + + g_free_not_null(rec->arg); + g_free(rec->name); + g_free(rec); + } + g_slist_free(value); +} + +static void server_eventgrouptable_destroy(gpointer key, GSList *value) +{ + g_slist_foreach(value, (GFunc) g_free, NULL); + g_slist_free(value); +} + +static void server_cmdtable_destroy(char *key, REDIRECT_CMD_REC *value) +{ + g_free(key); + + g_slist_foreach(value->events, (GFunc) g_free, NULL); + g_slist_free(value->events); + g_free(value); +} + +static void sig_disconnected(SERVER_REC *server) +{ + g_return_if_fail(IS_SERVER(server)); + + if (server->eventtable != NULL) { + g_hash_table_foreach(server->eventtable, + (GHFunc) server_eventtable_destroy, NULL); + g_hash_table_destroy(server->eventtable); + } + + g_hash_table_foreach(server->eventgrouptable, + (GHFunc) server_eventgrouptable_destroy, NULL); + g_hash_table_destroy(server->eventgrouptable); + + if (server->cmdtable != NULL) { + g_hash_table_foreach(server->cmdtable, + (GHFunc) server_cmdtable_destroy, NULL); + g_hash_table_destroy(server->cmdtable); + } +} + +void server_redirect_initv(SERVER_REC *server, const char *command, + int last, GSList *list) +{ + REDIRECT_CMD_REC *rec; + + g_return_if_fail(IS_SERVER(server)); + g_return_if_fail(command != NULL); + g_return_if_fail(last > 0); + + if (g_hash_table_lookup(server->cmdtable, command) != NULL) { + /* already in hash table. list of events SHOULD be the same. */ + g_slist_foreach(list, (GFunc) g_free, NULL); + g_slist_free(list); + return; + } + + rec = g_new(REDIRECT_CMD_REC, 1); + rec->last = last; + rec->events = list; + g_hash_table_insert(server->cmdtable, g_strdup(command), rec); +} + +void server_redirect_init(SERVER_REC *server, const char *command, + int last, ...) +{ + va_list args; + GSList *list; + char *event; + + va_start(args, last); + list = NULL; + while ((event = va_arg(args, gchar *)) != NULL) + list = g_slist_append(list, g_strdup(event)); + va_end(args); + + server_redirect_initv(server, command, last, list); +} + +int server_redirect_single_event(SERVER_REC *server, const char *arg, + int last, int group, const char *event, + const char *signal, int argpos) +{ + REDIRECT_REC *rec; + GSList *list, *grouplist; + char *origkey; + + g_return_val_if_fail(IS_SERVER(server), 0); + g_return_val_if_fail(event != NULL, 0); + g_return_val_if_fail(signal != NULL, 0); + g_return_val_if_fail(arg != NULL || argpos == -1, 0); + + if (group == 0) group = ++redirect_group; + + rec = g_new0(REDIRECT_REC, 1); + rec->arg = arg == NULL ? NULL : g_strdup(arg); + rec->argpos = argpos; + rec->name = g_strdup(signal); + rec->group = group; + rec->last = last; + + if (g_hash_table_lookup_extended(server->eventtable, event, + (gpointer *) &origkey, + (gpointer *) &list)) { + g_hash_table_remove(server->eventtable, origkey); + } else { + list = NULL; + origkey = g_strdup(event); + } + + grouplist = g_hash_table_lookup(server->eventgrouptable, + GINT_TO_POINTER(group)); + if (grouplist != NULL) { + g_hash_table_remove(server->eventgrouptable, + GINT_TO_POINTER(group)); + } + + list = g_slist_append(list, rec); + grouplist = g_slist_append(grouplist, g_strdup(event)); + + g_hash_table_insert(server->eventtable, origkey, list); + g_hash_table_insert(server->eventgrouptable, + GINT_TO_POINTER(group), grouplist); + + return group; +} + +void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...) +{ + va_list args; + char *event, *signal; + int argpos, group; + + g_return_if_fail(IS_SERVER(server)); + + va_start(args, last); + + group = 0; + while ((event = va_arg(args, gchar *)) != NULL) { + signal = va_arg(args, gchar *); + argpos = va_arg(args, gint); + + group = server_redirect_single_event(server, arg, last > 0, + group, event, signal, + argpos); + last--; + } + + va_end(args); +} + +void server_redirect_default(SERVER_REC *server, const char *command) +{ + REDIRECT_CMD_REC *cmdrec; + REDIRECT_REC *rec; + GSList *events, *list, *grouplist; + char *event, *origkey; + int last; + + g_return_if_fail(IS_SERVER(server)); + g_return_if_fail(command != NULL); + + if (server->cmdtable == NULL) + return; /* not connected yet */ + + cmdrec = g_hash_table_lookup(server->cmdtable, command); + if (cmdrec == NULL) return; + + /* add all events used by command to eventtable and eventgrouptable */ + redirect_group++; grouplist = NULL; last = cmdrec->last; + for (events = cmdrec->events; events != NULL; events = events->next) { + event = events->data; + + if (g_hash_table_lookup_extended(server->eventtable, event, + (gpointer *) &origkey, + (gpointer *) &list)) { + g_hash_table_remove(server->eventtable, origkey); + } else { + list = NULL; + origkey = g_strdup(event); + } + + rec = g_new0(REDIRECT_REC, 1); + rec->argpos = -1; + rec->name = g_strdup(event); + rec->group = redirect_group; + rec->last = last > 0; + + grouplist = g_slist_append(grouplist, g_strdup(event)); + list = g_slist_append(list, rec); + g_hash_table_insert(server->eventtable, origkey, list); + + last--; + } + + g_hash_table_insert(server->eventgrouptable, + GINT_TO_POINTER(redirect_group), grouplist); +} + +void server_redirect_remove_next(SERVER_REC *server, const char *event, + GSList *item) +{ + REDIRECT_REC *rec; + GSList *grouplist, *list, *events, *tmp; + char *origkey; + int group; + + g_return_if_fail(IS_SERVER(server)); + g_return_if_fail(event != NULL); + + if (!g_hash_table_lookup_extended(server->eventtable, event, + (gpointer *) &origkey, + (gpointer *) &list)) + return; + + rec = item == NULL ? list->data : item->data; + if (!rec->last) { + /* this wasn't last expected event */ + return; + } + group = rec->group; + + /* get list of events from this group */ + grouplist = g_hash_table_lookup(server->eventgrouptable, + GINT_TO_POINTER(group)); + + /* remove all of them */ + for (list = grouplist; list != NULL; list = list->next) { + char *event = list->data; + + if (!g_hash_table_lookup_extended(server->eventtable, event, + (gpointer *) &origkey, + (gpointer *) &events)) { + g_warning("server_redirect_remove_next() : " + "event in eventgrouptable but not in " + "eventtable"); + continue; + } + + /* remove the right group */ + for (tmp = events; tmp != NULL; tmp = tmp->next) { + rec = tmp->data; + + if (rec->group == group) + break; + } + + if (rec == NULL) { + g_warning("server_redirect_remove_next() : " + "event in eventgrouptable but not in " + "eventtable (group)"); + continue; + } + + g_free(event); + + events = g_slist_remove(events, rec); + g_free_not_null(rec->arg); + g_free(rec->name); + g_free(rec); + + /* update hash table */ + g_hash_table_remove(server->eventtable, origkey); + if (events == NULL) + g_free(origkey); + else { + g_hash_table_insert(server->eventtable, + origkey, events); + } + } + + g_hash_table_remove(server->eventgrouptable, GINT_TO_POINTER(group)); + g_slist_free(grouplist); +} + +GSList *server_redirect_getqueue(SERVER_REC *server, const char *event, + const char *args) +{ + REDIRECT_REC *rec; + GSList *list; + char **arglist; + int found; + + g_return_val_if_fail(IS_SERVER(server), NULL); + g_return_val_if_fail(event != NULL, NULL); + + list = g_hash_table_lookup(server->eventtable, event); + + for (; list != NULL; list = list->next) { + rec = list->data; + if (rec->argpos == -1) + break; + + if (rec->arg == NULL || args == NULL) + continue; + + /* we need to check that the argument is right.. */ + arglist = g_strsplit(args, " ", -1); + found = (strarray_length(arglist) > rec->argpos && + find_substr(rec->arg, arglist[rec->argpos])); + g_strfreev(arglist); + + if (found) break; + } + + return list; +} + +void servers_redirect_init(void) +{ + redirect_group = 0; + + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} + +void servers_redirect_deinit(void) +{ + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); +} diff --git a/apps/irssi/src/core/servers-redirect.h b/apps/irssi/src/core/servers-redirect.h new file mode 100644 index 00000000..c9e45ce3 --- /dev/null +++ b/apps/irssi/src/core/servers-redirect.h @@ -0,0 +1,36 @@ +#ifndef __SERVERS_REDIRECT_H +#define __SERVERS_REDIRECT_H + +typedef struct { + int last; /* number of "last" events at the start of the events list */ + GSList *events; /* char* list of events */ +} REDIRECT_CMD_REC; + +typedef struct { + char *name; /* event name */ + + char *arg; /* argument for event we are expecting or NULL */ + int argpos; /* argument position */ + + int group; /* group of events this belongs to */ + int last; /* if this event is received, remove all the events in this group */ +} +REDIRECT_REC; + +void server_redirect_init(SERVER_REC *server, const char *command, int last, ...); +void server_redirect_initv(SERVER_REC *server, const char *command, int last, GSList *list); +/* ... = char *event1, char *event2, ..., NULL */ + +void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...); +/* ... = char *event, char *callback_signal, int argpos, ..., NULL */ + +int server_redirect_single_event(SERVER_REC *server, const char *arg, int last, int group, + const char *event, const char *signal, int argpos); +void server_redirect_default(SERVER_REC *server, const char *command); +void server_redirect_remove_next(SERVER_REC *server, const char *event, GSList *item); +GSList *server_redirect_getqueue(SERVER_REC *server, const char *event, const char *args); + +void servers_redirect_init(void); +void servers_redirect_deinit(void); + +#endif diff --git a/apps/irssi/src/core/servers-setup.c b/apps/irssi/src/core/servers-setup.c new file mode 100644 index 00000000..deaf3cc9 --- /dev/null +++ b/apps/irssi/src/core/servers-setup.c @@ -0,0 +1,532 @@ +/* + servers-setup.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "network.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "chatnets.h" +#include "servers.h" +#include "servers-setup.h" + +GSList *setupservers; + +char *old_source_host; +int source_host_ok; /* Use source_host_ip .. */ +IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */ + +static void save_ips(IPADDR *ip4, IPADDR *ip6, + IPADDR **save_ip4, IPADDR **save_ip6) +{ + if (ip4->family == 0) + g_free_and_null(*save_ip4); + else { + if (*save_ip4 == NULL) + *save_ip4 = g_new(IPADDR, 1); + memcpy(*save_ip4, ip4, sizeof(IPADDR)); + } + + if (ip6->family == 0) + g_free_and_null(*save_ip6); + else { + if (*save_ip6 == NULL) + *save_ip6 = g_new(IPADDR, 1); + memcpy(*save_ip6, ip6, sizeof(IPADDR)); + } +} + +static void get_source_host_ip(void) +{ + const char *hostname; + IPADDR ip4, ip6; + + if (source_host_ok) + return; + + /* FIXME: This will block! */ + hostname = settings_get_str("hostname"); + source_host_ok = *hostname != '\0' && + net_gethostbyname(hostname, &ip4, &ip6) == 0; + + if (source_host_ok) + save_ips(&ip4, &ip6, &source_host_ip4, &source_host_ip6); + else { + g_free_and_null(source_host_ip4); + g_free_and_null(source_host_ip6); + } +} + +static void conn_set_ip(SERVER_CONNECT_REC *conn, const char *own_host, + IPADDR **own_ip4, IPADDR **own_ip6) +{ + IPADDR ip4, ip6; + + if (*own_ip4 == NULL && *own_ip6 == NULL) { + /* resolve the IP */ + if (net_gethostbyname(own_host, &ip4, &ip6) == 0) + save_ips(&ip4, &ip6, own_ip4, own_ip6); + } + + server_connect_own_ip_save(conn, *own_ip4, *own_ip6); +} + +/* Fill information to connection from server setup record */ +void server_setup_fill_reconn(SERVER_CONNECT_REC *conn, + SERVER_SETUP_REC *sserver) +{ + g_return_if_fail(IS_SERVER_CONNECT(conn)); + g_return_if_fail(IS_SERVER_SETUP(sserver)); + + if (sserver->own_host != NULL) { + conn_set_ip(conn, sserver->own_host, + &sserver->own_ip4, &sserver->own_ip6); + } + + if (sserver->chatnet != NULL && conn->chatnet == NULL) + conn->chatnet = g_strdup(sserver->chatnet); + + if (sserver->password != NULL && conn->password == NULL) + conn->password = g_strdup(sserver->password); + + signal_emit("server setup fill reconn", 2, conn, sserver); +} + +static void server_setup_fill(SERVER_CONNECT_REC *conn, + const char *address, int port) +{ + g_return_if_fail(conn != NULL); + g_return_if_fail(address != NULL); + + conn->type = module_get_uniq_id("SERVER CONNECT", 0); + + conn->address = g_strdup(address); + if (port > 0) conn->port = port; + + if (!conn->nick) conn->nick = g_strdup(settings_get_str("nick")); + conn->username = g_strdup(settings_get_str("user_name")); + conn->realname = g_strdup(settings_get_str("real_name")); + + /* proxy settings */ + if (settings_get_bool("use_proxy")) { + conn->proxy = g_strdup(settings_get_str("proxy_address")); + conn->proxy_port = settings_get_int("proxy_port"); + conn->proxy_string = g_strdup(settings_get_str("proxy_string")); + conn->proxy_password = g_strdup(settings_get_str("proxy_password")); + } + + /* source IP */ + if (source_host_ip4 != NULL) { + conn->own_ip4 = g_new(IPADDR, 1); + memcpy(conn->own_ip4, source_host_ip4, sizeof(IPADDR)); + } + if (source_host_ip6 != NULL) { + conn->own_ip6 = g_new(IPADDR, 1); + memcpy(conn->own_ip6, source_host_ip6, sizeof(IPADDR)); + } +} + +static void server_setup_fill_server(SERVER_CONNECT_REC *conn, + SERVER_SETUP_REC *sserver) +{ + g_return_if_fail(IS_SERVER_CONNECT(conn)); + g_return_if_fail(IS_SERVER_SETUP(sserver)); + + sserver->last_connect = time(NULL); + + if (sserver->family != 0 && conn->family == 0) + conn->family = sserver->family; + if (sserver->port > 0 && conn->port <= 0) + conn->port = sserver->port; + server_setup_fill_reconn(conn, sserver); + + signal_emit("server setup fill server", 2, conn, sserver); +} + +static void server_setup_fill_chatnet(SERVER_CONNECT_REC *conn, + CHATNET_REC *chatnet) +{ + g_return_if_fail(IS_SERVER_CONNECT(conn)); + g_return_if_fail(IS_CHATNET(chatnet)); + + if (chatnet->nick) { + g_free(conn->nick); + conn->nick = g_strdup(chatnet->nick);; + } + if (chatnet->username) { + g_free(conn->username); + conn->username = g_strdup(chatnet->username);; + } + if (chatnet->realname) { + g_free(conn->realname); + conn->realname = g_strdup(chatnet->realname);; + } + if (chatnet->own_host != NULL) { + conn_set_ip(conn, chatnet->own_host, + &chatnet->own_ip4, &chatnet->own_ip6); + } + + signal_emit("server setup fill chatnet", 2, conn, chatnet); +} + +static SERVER_CONNECT_REC * +create_addr_conn(int chat_type, const char *address, int port, + const char *chatnet, const char *password, + const char *nick) +{ + CHAT_PROTOCOL_REC *proto; + SERVER_CONNECT_REC *conn; + SERVER_SETUP_REC *sserver; + CHATNET_REC *chatnetrec; + + g_return_val_if_fail(address != NULL, NULL); + + sserver = server_setup_find(address, port); + if (sserver != NULL) { + if (chat_type < 0) + chat_type = sserver->chat_type; + else if (chat_type != sserver->chat_type) + sserver = NULL; + } + + proto = chat_type >= 0 ? chat_protocol_find_id(chat_type) : + chat_protocol_get_default(); + + conn = proto->create_server_connect(); + conn->chat_type = proto->id; + if (chatnet != NULL && *chatnet != '\0') + conn->chatnet = g_strdup(chatnet); + + /* fill in the defaults */ + server_setup_fill(conn, address, port); + + /* fill the rest from chat network settings */ + chatnetrec = chatnet != NULL ? chatnet_find(chatnet) : + (sserver == NULL || sserver->chatnet == NULL ? NULL : + chatnet_find(sserver->chatnet)); + if (chatnetrec != NULL) + server_setup_fill_chatnet(conn, chatnetrec); + + /* fill the information from setup */ + if (sserver != NULL) + server_setup_fill_server(conn, sserver); + + /* nick / password given in command line overrides all settings */ + if (password && *password) { + g_free_not_null(conn->password); + conn->password = g_strdup(password); + } + if (nick && *nick) { + g_free_not_null(conn->nick); + conn->nick = g_strdup(nick); + } + + signal_emit("server setup fill connect", 1, conn); + return conn; +} + +/* Connect to server where last connect succeeded (or we haven't tried to + connect yet). If there's no such server, connect to server where we + haven't connected for the longest time */ +static SERVER_CONNECT_REC * +create_chatnet_conn(const char *dest, int port, + const char *password, const char *nick) +{ + SERVER_SETUP_REC *bestrec; + GSList *tmp; + time_t now, besttime; + + now = time(NULL); + bestrec = NULL; besttime = now; + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SERVER_SETUP_REC *rec = tmp->data; + + if (rec->chatnet == NULL || + g_strcasecmp(rec->chatnet, dest) != 0) + continue; + + if (!rec->last_failed) { + bestrec = rec; + break; + } + + if (bestrec == NULL || besttime > rec->last_connect) { + bestrec = rec; + besttime = rec->last_connect; + } + } + + return bestrec == NULL ? NULL : + create_addr_conn(bestrec->chat_type, bestrec->address, 0, + dest, NULL, nick); +} + +/* Create server connection record. `dest' is required, rest can be NULL. + `dest' is either a server address or chat network */ +SERVER_CONNECT_REC * +server_create_conn(int chat_type, const char *dest, int port, + const char *chatnet, const char *password, + const char *nick) +{ + SERVER_CONNECT_REC *rec; + + g_return_val_if_fail(dest != NULL, NULL); + + if (chatnet_find(dest) != NULL) { + rec = create_chatnet_conn(dest, port, password, nick); + if (rec != NULL) + return rec; + } + + return create_addr_conn(chat_type, dest, port, + chatnet, password, nick); +} + +/* Find matching server from setup. Try to find record with a same port, + but fallback to any server with the same address. */ +SERVER_SETUP_REC *server_setup_find(const char *address, int port) +{ + SERVER_SETUP_REC *server; + GSList *tmp; + + g_return_val_if_fail(address != NULL, NULL); + + server = NULL; + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SERVER_SETUP_REC *rec = tmp->data; + + if (g_strcasecmp(rec->address, address) == 0) { + server = rec; + if (rec->port == port) + break; + } + } + + return server; +} + +/* Find matching server from setup. Ports must match or NULL is returned. */ +SERVER_SETUP_REC *server_setup_find_port(const char *address, int port) +{ + SERVER_SETUP_REC *rec; + + rec = server_setup_find(address, port); + return rec == NULL || rec->port != port ? NULL : rec; +} + +static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) +{ + SERVER_SETUP_REC *rec; + CHATNET_REC *chatnetrec; + char *server, *chatnet, *family; + int port; + + g_return_val_if_fail(node != NULL, NULL); + + server = config_node_get_str(node, "address", NULL); + if (server == NULL) + return NULL; + + port = config_node_get_int(node, "port", 0); + if (server_setup_find_port(server, port) != NULL) { + /* already exists - don't let it get there twice or + server reconnects will screw up! */ + return NULL; + } + + rec = NULL; + chatnet = config_node_get_str(node, "chatnet", NULL); + if (chatnet == NULL) /* FIXME: remove this after .98... */ { + chatnet = config_node_get_str(node, "ircnet", NULL); + if (chatnet != NULL) { + iconfig_node_set_str(node, "chatnet", chatnet); + iconfig_node_set_str(node, "ircnet", NULL); + chatnet = config_node_get_str(node, "chatnet", NULL); + } + } + + chatnetrec = chatnet == NULL ? NULL : chatnet_find(chatnet); + if (chatnetrec == NULL && chatnet != NULL) { + /* chat network not found, create it. */ + chatnetrec = chat_protocol_get_default()->create_chatnet(); + chatnetrec->chat_type = chat_protocol_get_default()->id; + chatnetrec->name = g_strdup(chatnet); + chatnet_create(chatnetrec); + } + + family = config_node_get_str(node, "family", ""); + + rec = CHAT_PROTOCOL(chatnetrec)->create_server_setup(); + rec->type = module_get_uniq_id("SERVER SETUP", 0); + rec->chat_type = CHAT_PROTOCOL(chatnetrec)->id; + rec->chatnet = chatnetrec == NULL ? NULL : g_strdup(chatnetrec->name); + rec->family = g_strcasecmp(family, "inet6") == 0 ? AF_INET6 : + (g_strcasecmp(family, "inet") == 0 ? AF_INET : 0); + rec->address = g_strdup(server); + rec->password = g_strdup(config_node_get_str(node, "password", NULL)); + rec->port = port; + rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE); + rec->own_host = g_strdup(config_node_get_str(node, "own_host", NULL)); + + signal_emit("server setup read", 2, rec, node); + + setupservers = g_slist_append(setupservers, rec); + return rec; +} + +static void server_setup_save(SERVER_SETUP_REC *rec) +{ + CONFIG_NODE *parentnode, *node; + int index; + + index = g_slist_index(setupservers, rec); + + parentnode = iconfig_node_traverse("(servers", TRUE); + node = config_node_index(parentnode, index); + if (node == NULL) + node = config_node_section(parentnode, NULL, NODE_TYPE_BLOCK); + + iconfig_node_clear(node); + iconfig_node_set_str(node, "address", rec->address); + iconfig_node_set_str(node, "chatnet", rec->chatnet); + + iconfig_node_set_int(node, "port", rec->port); + iconfig_node_set_str(node, "password", rec->password); + iconfig_node_set_str(node, "own_host", rec->own_host); + + iconfig_node_set_str(node, "family", + rec->family == AF_INET6 ? "inet6" : + rec->family == AF_INET ? "inet" : NULL); + + if (rec->autoconnect) + iconfig_node_set_bool(node, "autoconnect", TRUE); + + signal_emit("server setup saved", 2, rec, node); +} + +static void server_setup_remove_config(SERVER_SETUP_REC *rec) +{ + CONFIG_NODE *node; + int index; + + node = iconfig_node_traverse("servers", FALSE); + if (node != NULL) { + index = g_slist_index(setupservers, rec); + iconfig_node_list_remove(node, index); + } +} + +static void server_setup_destroy(SERVER_SETUP_REC *rec) +{ + setupservers = g_slist_remove(setupservers, rec); + signal_emit("server setup destroyed", 1, rec); + + g_free_not_null(rec->own_host); + g_free_not_null(rec->own_ip4); + g_free_not_null(rec->own_ip6); + g_free_not_null(rec->chatnet); + g_free_not_null(rec->password); + g_free(rec->address); + g_free(rec); +} + +void server_setup_add(SERVER_SETUP_REC *rec) +{ + rec->type = module_get_uniq_id("SERVER SETUP", 0); + if (g_slist_find(setupservers, rec) == NULL) + setupservers = g_slist_append(setupservers, rec); + server_setup_save(rec); +} + +void server_setup_remove(SERVER_SETUP_REC *rec) +{ + server_setup_remove_config(rec); + server_setup_destroy(rec); +} + +static void read_servers(void) +{ + CONFIG_NODE *node; + GSList *tmp; + + while (setupservers != NULL) + server_setup_destroy(setupservers->data); + + /* Read servers */ + node = iconfig_node_traverse("servers", FALSE); + if (node != NULL) { + for (tmp = node->value; tmp != NULL; tmp = tmp->next) + server_setup_read(tmp->data); + } +} + +static void read_settings(void) +{ + if (old_source_host == NULL || + strcmp(old_source_host, settings_get_str("hostname")) != 0) { + g_free_not_null(old_source_host); + old_source_host = g_strdup(settings_get_str("hostname")); + + source_host_ok = FALSE; + get_source_host_ip(); + } +} + +void servers_setup_init(void) +{ + settings_add_str("server", "hostname", ""); + + settings_add_str("server", "nick", NULL); + settings_add_str("server", "user_name", NULL); + settings_add_str("server", "real_name", NULL); + + settings_add_bool("proxy", "use_proxy", FALSE); + settings_add_str("proxy", "proxy_address", ""); + settings_add_int("proxy", "proxy_port", 6667); + settings_add_str("proxy", "proxy_string", "CONNECT %s %d"); + settings_add_str("proxy", "proxy_password", ""); + + setupservers = NULL; + source_host_ip4 = source_host_ip6 = NULL; + old_source_host = NULL; + read_settings(); + + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add("setup reread", (SIGNAL_FUNC) read_servers); + signal_add("irssi init read settings", (SIGNAL_FUNC) read_servers); +} + +void servers_setup_deinit(void) +{ + g_free_not_null(source_host_ip4); + g_free_not_null(source_host_ip6); + g_free_not_null(old_source_host); + + while (setupservers != NULL) + server_setup_destroy(setupservers->data); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("setup reread", (SIGNAL_FUNC) read_servers); + signal_remove("irssi init read settings", (SIGNAL_FUNC) read_servers); + + module_uniq_destroy("SERVER SETUP"); +} diff --git a/apps/irssi/src/core/servers-setup.h b/apps/irssi/src/core/servers-setup.h new file mode 100644 index 00000000..d0807d11 --- /dev/null +++ b/apps/irssi/src/core/servers-setup.h @@ -0,0 +1,46 @@ +#ifndef __SERVERS_SETUP_H +#define __SERVERS_SETUP_H + +#include "modules.h" + +#define SERVER_SETUP(server) \ + MODULE_CHECK_CAST(server, SERVER_SETUP_REC, type, "SERVER SETUP") + +#define IS_SERVER_SETUP(server) \ + (SERVER_SETUP(server) ? TRUE : FALSE) + +/* servers */ +struct _SERVER_SETUP_REC { +#include "server-setup-rec.h" +}; + +extern GSList *setupservers; + +extern IPADDR *source_host_ip4, *source_host_ip6; /* Resolved address */ +extern int source_host_ok; /* Use source_host_ip .. */ + +/* Fill reconnection specific information to connection + from server setup record */ +void server_setup_fill_reconn(SERVER_CONNECT_REC *conn, + SERVER_SETUP_REC *sserver); + +/* Create server connection record. `dest' is required, rest can be NULL. + `dest' is either a server address or chat network */ +SERVER_CONNECT_REC * +server_create_conn(int chat_type, const char *dest, int port, + const char *chatnet, const char *password, + const char *nick); + +/* Find matching server from setup. Try to find record with a same port, + but fallback to any server with the same address. */ +SERVER_SETUP_REC *server_setup_find(const char *address, int port); +/* Find matching server from setup. Ports must match or NULL is returned. */ +SERVER_SETUP_REC *server_setup_find_port(const char *address, int port); + +void server_setup_add(SERVER_SETUP_REC *rec); +void server_setup_remove(SERVER_SETUP_REC *rec); + +void servers_setup_init(void); +void servers_setup_deinit(void); + +#endif diff --git a/apps/irssi/src/core/servers.c b/apps/irssi/src/core/servers.c new file mode 100644 index 00000000..f74e7f58 --- /dev/null +++ b/apps/irssi/src/core/servers.c @@ -0,0 +1,544 @@ +/* + server.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "line-split.h" +#include "net-nonblock.h" +#include "net-sendbuffer.h" +#include "misc.h" +#include "rawlog.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "servers.h" +#include "servers-reconnect.h" +#include "servers-redirect.h" +#include "servers-setup.h" +#include "channels.h" +#include "queries.h" + +GSList *servers, *lookup_servers; + +/* connection to server failed */ +void server_connect_failed(SERVER_REC *server, const char *msg) +{ + g_return_if_fail(IS_SERVER(server)); + + lookup_servers = g_slist_remove(lookup_servers, server); + + signal_emit("server connect failed", 2, server, msg); + if (server->connect_tag != -1) + g_source_remove(server->connect_tag); + if (server->handle != NULL) + net_sendbuffer_destroy(server->handle, TRUE); + + if (server->connect_pipe[0] != NULL) { + g_io_channel_close(server->connect_pipe[0]); + g_io_channel_unref(server->connect_pipe[0]); + g_io_channel_close(server->connect_pipe[1]); + g_io_channel_unref(server->connect_pipe[1]); + } + + MODULE_DATA_DEINIT(server); + server_connect_free(server->connrec); + g_free_not_null(server->nick); + g_free(server->tag); + g_free(server); +} + +/* generate tag from server's address */ +static char *server_create_address_tag(const char *address) +{ + const char *start, *end; + + g_return_val_if_fail(address != NULL, NULL); + + /* try to generate a reasonable server tag */ + if (strchr(address, '.') == NULL) { + start = end = NULL; + } else if (g_strncasecmp(address, "irc", 3) == 0 || + g_strncasecmp(address, "chat", 4) == 0) { + /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */ + end = strrchr(address, '.'); + start = end-1; + while (start > address && *start != '.') start--; + } else { + /* efnet.cs.hut.fi -> efnet */ + end = strchr(address, '.'); + start = end; + } + + if (start == end) start = address; else start++; + if (end == NULL) end = address + strlen(address); + + return g_strndup(start, (int) (end-start)); +} + +/* create unique tag for server. prefer ircnet's name or + generate it from server's address */ +static char *server_create_tag(SERVER_CONNECT_REC *conn) +{ + GString *str; + char *tag; + int num; + + g_return_val_if_fail(IS_SERVER_CONNECT(conn), NULL); + + tag = conn->chatnet != NULL && *conn->chatnet != '\0' ? + g_strdup(conn->chatnet) : + server_create_address_tag(conn->address); + + /* then just append numbers after tag until unused is found.. */ + str = g_string_new(tag); + for (num = 2; server_find_tag(str->str) != NULL; num++) + g_string_sprintf(str, "%s%d", tag, num); + g_free(tag); + + tag = str->str; + g_string_free(str, FALSE); + return tag; +} + +/* Connection to server finished, fill the rest of the fields */ +void server_connect_finished(SERVER_REC *server) +{ + server->connect_time = time(NULL); + server->rawlog = rawlog_create(); + + server->eventtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + server->eventgrouptable = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); + server->cmdtable = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); + + servers = g_slist_append(servers, server); + signal_emit("server connected", 1, server); +} + +static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle) +{ + int error; + + g_return_if_fail(IS_SERVER(server)); + + error = net_geterror(handle); + if (error != 0) { + server->connection_lost = TRUE; + server_connect_failed(server, g_strerror(error)); + return; + } + + lookup_servers = g_slist_remove(lookup_servers, server); + g_source_remove(server->connect_tag); + server->connect_tag = -1; + + server_connect_finished(server); +} + +static void server_connect_callback_readpipe(SERVER_REC *server) +{ + SERVER_CONNECT_REC *conn; + RESOLVED_IP_REC iprec; + GIOChannel *handle; + IPADDR *ip, *own_ip; + const char *errormsg; + int port; + + g_return_if_fail(IS_SERVER(server)); + + g_source_remove(server->connect_tag); + server->connect_tag = -1; + + net_gethostbyname_return(server->connect_pipe[0], &iprec); + + g_io_channel_close(server->connect_pipe[0]); + g_io_channel_unref(server->connect_pipe[0]); + g_io_channel_close(server->connect_pipe[1]); + g_io_channel_unref(server->connect_pipe[1]); + + server->connect_pipe[0] = NULL; + server->connect_pipe[1] = NULL; + + /* figure out if we should use IPv4 or v6 address */ + ip = iprec.error != 0 ? NULL : iprec.ip6.family == 0 || + (server->connrec->family == AF_INET && iprec.ip4.family != 0) ? + &iprec.ip4 : &iprec.ip6; + if (iprec.ip4.family != 0 && server->connrec->family == 0 && + !settings_get_bool("resolve_prefer_ipv6")) + ip = &iprec.ip4; + + conn = server->connrec; + port = conn->proxy != NULL ? conn->proxy_port : conn->port; + own_ip = ip == NULL ? NULL : + (IPADDR_IS_V6(ip) ? conn->own_ip6 : conn->own_ip4); + + if (ip != NULL) + signal_emit("server connecting", 2, server, ip); + + handle = ip == NULL ? NULL : net_connect_ip(ip, port, own_ip); + if (handle == NULL) { + /* failed */ + if (iprec.error != 0 && net_hosterror_notfound(iprec.error)) { + /* IP wasn't found for the host, don't try to reconnect + back to this server */ + server->dns_error = TRUE; + } + + if (iprec.error == 0) { + /* connect() failed */ + errormsg = g_strerror(errno); + } else { + /* gethostbyname() failed */ + errormsg = iprec.errorstr != NULL ? iprec.errorstr : + "Host lookup failed"; + } + server->connection_lost = TRUE; + server_connect_failed(server, errormsg); + g_free_not_null(iprec.errorstr); + return; + } + + server->handle = net_sendbuffer_create(handle, 0); + server->connect_tag = + g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ, + (GInputFunction) server_connect_callback_init, + server); +} + +/* initializes server record but doesn't start connecting */ +void server_connect_init(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + MODULE_DATA_INIT(server); + server->type = module_get_uniq_id("SERVER", 0); + + server->nick = g_strdup(server->connrec->nick); + if (server->connrec->username == NULL || *server->connrec->username == '\0') { + g_free_not_null(server->connrec->username); + + server->connrec->username = g_get_user_name(); + if (*server->connrec->username == '\0') server->connrec->username = "-"; + server->connrec->username = g_strdup(server->connrec->username); + } + if (server->connrec->realname == NULL || *server->connrec->realname == '\0') { + g_free_not_null(server->connrec->realname); + + server->connrec->realname = g_get_real_name(); + if (*server->connrec->realname == '\0') server->connrec->realname = "-"; + server->connrec->realname = g_strdup(server->connrec->realname); + } + + server->tag = server_create_tag(server->connrec); +} + +/* starts connecting to server */ +int server_start_connect(SERVER_REC *server) +{ + const char *connect_address; + int fd[2]; + + g_return_val_if_fail(server != NULL, FALSE); + if (server->connrec->port <= 0) return FALSE; + + server_connect_init(server); + + if (pipe(fd) != 0) { + g_warning("server_connect(): pipe() failed."); + g_free(server->tag); + g_free(server->nick); + return FALSE; + } + + server->connect_pipe[0] = g_io_channel_unix_new(fd[0]); + server->connect_pipe[1] = g_io_channel_unix_new(fd[1]); + + connect_address = server->connrec->proxy != NULL ? + server->connrec->proxy : server->connrec->address; + server->connect_pid = + net_gethostbyname_nonblock(connect_address, + server->connect_pipe[1]); + server->connect_tag = + g_input_add(server->connect_pipe[0], G_INPUT_READ, + (GInputFunction) server_connect_callback_readpipe, + server); + + lookup_servers = g_slist_append(lookup_servers, server); + + signal_emit("server looking", 1, server); + return TRUE; +} + +static int server_remove_channels(SERVER_REC *server) +{ + GSList *tmp; + int found; + + g_return_val_if_fail(server != NULL, FALSE); + + found = FALSE; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + channel->server = NULL; + channel_destroy(channel); + found = TRUE; + } + + while (server->queries != NULL) + query_change_server(server->queries->data, NULL); + + g_slist_free(server->channels); + g_slist_free(server->queries); + + return found; +} + +void server_disconnect(SERVER_REC *server) +{ + int chans; + + g_return_if_fail(IS_SERVER(server)); + + if (server->connect_tag != -1) { + /* still connecting to server.. */ + if (server->connect_pid != -1) + net_disconnect_nonblock(server->connect_pid); + server_connect_failed(server, NULL); + return; + } + + servers = g_slist_remove(servers, server); + + signal_emit("server disconnected", 1, server); + + /* close all channels */ + chans = server_remove_channels(server); + + if (server->handle != NULL) { + if (!chans || server->connection_lost) + net_sendbuffer_destroy(server->handle, TRUE); + else { + /* we were on some channels, try to let the server + disconnect so that our quit message is guaranteed + to get displayed */ + net_disconnect_later(net_sendbuffer_handle(server->handle)); + net_sendbuffer_destroy(server->handle, FALSE); + } + server->handle = NULL; + } + + if (server->readtag > 0) + g_source_remove(server->readtag); + + MODULE_DATA_DEINIT(server); + server_connect_free(server->connrec); + rawlog_destroy(server->rawlog); + line_split_free(server->buffer); + g_free_not_null(server->version); + g_free_not_null(server->away_reason); + g_free(server->nick); + g_free(server->tag); + g_free(server); +} + +SERVER_REC *server_find_tag(const char *tag) +{ + GSList *tmp; + + g_return_val_if_fail(tag != NULL, NULL); + if (*tag == '\0') return NULL; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *server = tmp->data; + + if (g_strcasecmp(server->tag, tag) == 0) + return server; + } + + for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *server = tmp->data; + + if (g_strcasecmp(server->tag, tag) == 0) + return server; + } + + return NULL; +} + +SERVER_REC *server_find_chatnet(const char *chatnet) +{ + GSList *tmp; + + g_return_val_if_fail(chatnet != NULL, NULL); + if (*chatnet == '\0') return NULL; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *server = tmp->data; + + if (server->connrec->chatnet != NULL && + g_strcasecmp(server->connrec->chatnet, chatnet) == 0) + return server; + } + + return NULL; +} + +void server_connect_free(SERVER_CONNECT_REC *conn) +{ + g_return_if_fail(IS_SERVER_CONNECT(conn)); + + signal_emit("server connect free", 1, conn); + g_free_not_null(conn->proxy); + g_free_not_null(conn->proxy_string); + g_free_not_null(conn->proxy_password); + + g_free_not_null(conn->address); + g_free_not_null(conn->chatnet); + + g_free_not_null(conn->own_ip4); + g_free_not_null(conn->own_ip6); + + g_free_not_null(conn->password); + g_free_not_null(conn->nick); + g_free_not_null(conn->username); + g_free_not_null(conn->realname); + + g_free_not_null(conn->channels); + g_free_not_null(conn->away_reason); + g_free(conn); +} + +void server_change_nick(SERVER_REC *server, const char *nick) +{ + g_free(server->connrec->nick); + g_free(server->nick); + server->connrec->nick = g_strdup(nick); + server->nick = g_strdup(nick); + + signal_emit("server nick changed", 1, server); +} + +/* Update own IPv4 and IPv6 records */ +void server_connect_own_ip_save(SERVER_CONNECT_REC *conn, + IPADDR *ip4, IPADDR *ip6) +{ + if (ip4 == NULL || ip4->family == 0) + g_free_and_null(conn->own_ip4); + if (ip6 == NULL || ip6->family == 0) + g_free_and_null(conn->own_ip6); + + if (ip4 != NULL && ip4->family != 0) { + /* IPv4 address was found */ + if (conn->own_ip4 == NULL) + conn->own_ip4 = g_new0(IPADDR, 1); + memcpy(conn->own_ip4, ip4, sizeof(IPADDR)); + } + + if (ip6 != NULL && ip6->family != 0) { + /* IPv6 address was found */ + if (conn->own_ip6 == NULL) + conn->own_ip6 = g_new0(IPADDR, 1); + memcpy(conn->own_ip6, ip6, sizeof(IPADDR)); + } +} + +/* `optlist' should contain only one unknown key - the server tag. + returns NULL if there was unknown -option */ +SERVER_REC *cmd_options_get_server(const char *cmd, + GHashTable *optlist, + SERVER_REC *defserver) +{ + SERVER_REC *server; + GSList *list, *tmp, *next; + + /* get all the options, then remove the known ones. there should + be only one left - the server tag. */ + list = hashtable_get_keys(optlist); + if (cmd != NULL) { + for (tmp = list; tmp != NULL; tmp = next) { + char *option = tmp->data; + next = tmp->next; + + if (command_have_option(cmd, option)) + list = g_slist_remove(list, option); + } + } + + if (list == NULL) + return defserver; + + server = server_find_tag(list->data); + if (server == NULL || list->next != NULL) { + /* unknown option (not server tag) */ + signal_emit("error command", 2, + GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN), + server == NULL ? list->data : list->next->data); + signal_stop(); + + server = NULL; + } + + g_slist_free(list); + return server; +} + +static void disconnect_servers(GSList *servers, int chat_type) +{ + GSList *tmp, *next; + + for (tmp = servers; tmp != NULL; tmp = next) { + SERVER_REC *rec = tmp->data; + + next = tmp->next; + if (rec->chat_type == chat_type) + server_disconnect(rec); + } +} + +static void sig_chat_protocol_deinit(CHAT_PROTOCOL_REC *proto) +{ + disconnect_servers(servers, proto->id); + disconnect_servers(lookup_servers, proto->id); +} + +void servers_init(void) +{ + settings_add_bool("server", "resolve_prefer_ipv6", FALSE); + lookup_servers = servers = NULL; + + signal_add("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit); + + servers_reconnect_init(); + servers_redirect_init(); + servers_setup_init(); +} + +void servers_deinit(void) +{ + signal_remove("chat protocol deinit", (SIGNAL_FUNC) sig_chat_protocol_deinit); + + servers_setup_deinit(); + servers_redirect_deinit(); + servers_reconnect_deinit(); + + module_uniq_destroy("SERVER"); + module_uniq_destroy("SERVER CONNECT"); +} diff --git a/apps/irssi/src/core/servers.h b/apps/irssi/src/core/servers.h new file mode 100644 index 00000000..75e4cbf0 --- /dev/null +++ b/apps/irssi/src/core/servers.h @@ -0,0 +1,66 @@ +#ifndef __SERVERS_H +#define __SERVERS_H + +#include "modules.h" + +/* Returns SERVER_REC if it's server, NULL if it isn't. */ +#define SERVER(server) \ + MODULE_CHECK_CAST(server, SERVER_REC, type, "SERVER") + +/* Returns SERVER_CONNECT_REC if it's server connection, NULL if it isn't. */ +#define SERVER_CONNECT(conn) \ + MODULE_CHECK_CAST(conn, SERVER_CONNECT_REC, type, "SERVER CONNECT") + +#define IS_SERVER(server) \ + (SERVER(server) ? TRUE : FALSE) + +#define IS_SERVER_CONNECT(conn) \ + (SERVER_CONNECT(conn) ? TRUE : FALSE) + +/* all strings should be either NULL or dynamically allocated */ +/* address and nick are mandatory, rest are optional */ +struct _SERVER_CONNECT_REC { +#include "server-connect-rec.h" +}; + +#define STRUCT_SERVER_CONNECT_REC SERVER_CONNECT_REC +struct _SERVER_REC { +#include "server-rec.h" +}; + +extern GSList *servers, *lookup_servers; + +void servers_init(void); +void servers_deinit(void); + +/* Disconnect from server */ +void server_disconnect(SERVER_REC *server); + +SERVER_REC *server_find_tag(const char *tag); +SERVER_REC *server_find_chatnet(const char *chatnet); + +/* starts connecting to server */ +int server_start_connect(SERVER_REC *server); +void server_connect_free(SERVER_CONNECT_REC *conn); + +/* initializes server record but doesn't start connecting */ +void server_connect_init(SERVER_REC *server); +/* Connection to server finished, fill the rest of the fields */ +void server_connect_finished(SERVER_REC *server); +/* connection to server failed */ +void server_connect_failed(SERVER_REC *server, const char *msg); + +/* Change your nick */ +void server_change_nick(SERVER_REC *server, const char *nick); + +/* Update own IPv4 and IPv6 records */ +void server_connect_own_ip_save(SERVER_CONNECT_REC *conn, + IPADDR *ip4, IPADDR *ip6); + +/* `optlist' should contain only one unknown key - the server tag. + returns NULL if there was unknown -option */ +SERVER_REC *cmd_options_get_server(const char *cmd, + GHashTable *optlist, + SERVER_REC *defserver); + +#endif diff --git a/apps/irssi/src/core/settings.c b/apps/irssi/src/core/settings.c new file mode 100644 index 00000000..10ecba92 --- /dev/null +++ b/apps/irssi/src/core/settings.c @@ -0,0 +1,680 @@ +/* + settings.c : Irssi settings + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" + +#include "lib-config/iconfig.h" +#include "settings.h" +#include "default-config.h" + +#include + +CONFIG_REC *mainconfig; + +static GString *last_errors; +static char *last_config_error_msg; +static GSList *last_invalid_modules; +static int fe_initialized; +static int config_changed; /* FIXME: remove after .98 (unless needed again) */ + +static GHashTable *settings; +static int timeout_tag; + +static int config_last_modifycounter; +static time_t config_last_mtime; +static long config_last_size; +static unsigned int config_last_checksum; + +static SETTINGS_REC *settings_find(const char *key) +{ + SETTINGS_REC *rec; + + g_return_val_if_fail(key != NULL, NULL); + + rec = g_hash_table_lookup(settings, key); + if (rec == NULL) { + g_warning("settings_get_default_str(%s) : " + "unknown setting", key); + return NULL; + } + + return rec; +} + +const char *settings_get_str(const char *key) +{ + SETTINGS_REC *rec; + CONFIG_NODE *setnode, *node; + + rec = settings_find(key); + g_return_val_if_fail(rec != NULL, NULL); + + setnode = iconfig_node_traverse("settings", FALSE); + if (setnode == NULL) + return rec->def; + + node = config_node_section(setnode, rec->module, -1); + return node == NULL ? rec->def : + config_node_get_str(node, key, rec->def); +} + +int settings_get_int(const char *key) +{ + SETTINGS_REC *rec; + CONFIG_NODE *setnode, *node; + int def; + + rec = settings_find(key); + g_return_val_if_fail(rec != NULL, 0); + def = GPOINTER_TO_INT(rec->def); + + setnode = iconfig_node_traverse("settings", FALSE); + if (setnode == NULL) + return def; + + node = config_node_section(setnode, rec->module, -1); + return node == NULL ? def : + config_node_get_int(node, key, def); +} + +int settings_get_bool(const char *key) +{ + SETTINGS_REC *rec; + CONFIG_NODE *setnode, *node; + int def; + + rec = settings_find(key); + g_return_val_if_fail(rec != NULL, 0); + def = GPOINTER_TO_INT(rec->def); + + setnode = iconfig_node_traverse("settings", FALSE); + if (setnode == NULL) + return def; + + node = config_node_section(setnode, rec->module, -1); + return node == NULL ? def : + config_node_get_bool(node, key, def); +} + +void settings_add_str_module(const char *module, const char *section, + const char *key, const char *def) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + g_return_if_fail(section != NULL); + + rec = g_hash_table_lookup(settings, key); + g_return_if_fail(rec == NULL); + + rec = g_new0(SETTINGS_REC, 1); + rec->module = g_strdup(module); + rec->key = g_strdup(key); + rec->section = g_strdup(section); + rec->def = def == NULL ? NULL : g_strdup(def); + + g_hash_table_insert(settings, rec->key, rec); +} + +void settings_add_int_module(const char *module, const char *section, + const char *key, int def) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + g_return_if_fail(section != NULL); + + rec = g_hash_table_lookup(settings, key); + g_return_if_fail(rec == NULL); + + rec = g_new0(SETTINGS_REC, 1); + rec->module = g_strdup(module); + rec->type = SETTING_TYPE_INT; + rec->key = g_strdup(key); + rec->section = g_strdup(section); + rec->def = GINT_TO_POINTER(def); + + g_hash_table_insert(settings, rec->key, rec); +} + +void settings_add_bool_module(const char *module, const char *section, + const char *key, int def) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + g_return_if_fail(section != NULL); + + rec = g_hash_table_lookup(settings, key); + g_return_if_fail(rec == NULL); + + rec = g_new0(SETTINGS_REC, 1); + rec->module = g_strdup(module); + rec->type = SETTING_TYPE_BOOLEAN; + rec->key = g_strdup(key); + rec->section = g_strdup(section); + rec->def = GINT_TO_POINTER(def); + + g_hash_table_insert(settings, rec->key, rec); +} + +static void settings_destroy(SETTINGS_REC *rec) +{ + if (rec->type == SETTING_TYPE_STRING) + g_free_not_null(rec->def); + g_free(rec->module); + g_free(rec->section); + g_free(rec->key); + g_free(rec); +} + +void settings_remove(const char *key) +{ + SETTINGS_REC *rec; + + g_return_if_fail(key != NULL); + + rec = g_hash_table_lookup(settings, key); + if (rec == NULL) return; + + g_hash_table_remove(settings, key); + settings_destroy(rec); +} + +static int settings_remove_hash(const char *key, SETTINGS_REC *rec, + const char *module) +{ + if (strcmp(rec->module, module) == 0) { + settings_destroy(rec); + return TRUE; + } + + return FALSE; +} + +void settings_remove_module(const char *module) +{ + g_hash_table_foreach_remove(settings, + (GHRFunc) settings_remove_hash, + (void *) module); +} + +static CONFIG_NODE *settings_get_node(const char *key) +{ + SETTINGS_REC *rec; + CONFIG_NODE *node; + + g_return_val_if_fail(key != NULL, NULL); + + rec = g_hash_table_lookup(settings, key); + g_return_val_if_fail(rec != NULL, NULL); + + node = iconfig_node_traverse("settings", TRUE); + return config_node_section(node, rec->module, NODE_TYPE_BLOCK); +} + +void settings_set_str(const char *key, const char *value) +{ + iconfig_node_set_str(settings_get_node(key), key, value); +} + +void settings_set_int(const char *key, int value) +{ + iconfig_node_set_int(settings_get_node(key), key, value); +} + +void settings_set_bool(const char *key, int value) +{ + iconfig_node_set_bool(settings_get_node(key), key, value); +} + +int settings_get_type(const char *key) +{ + SETTINGS_REC *rec; + + g_return_val_if_fail(key != NULL, -1); + + rec = g_hash_table_lookup(settings, key); + return rec == NULL ? -1 : rec->type; +} + +/* Get the record of the setting */ +SETTINGS_REC *settings_get_record(const char *key) +{ + g_return_val_if_fail(key != NULL, NULL); + + return g_hash_table_lookup(settings, key); +} + +static void sig_init_finished(void) +{ + fe_initialized = TRUE; + if (last_errors != NULL) { + signal_emit("settings errors", 1, last_errors->str); + g_string_free(last_errors, TRUE); + } + + if (last_config_error_msg != NULL) { + signal_emit("gui dialog", 2, "error", last_config_error_msg); + g_free_and_null(last_config_error_msg); + } + + if (config_changed) { + /* some backwards compatibility changes were made to + config file, reload it */ + signal_emit("setup changed", 0); + } +} + +/* FIXME: remove after 0.7.98 - only for backward compatibility */ +static void settings_move(SETTINGS_REC *rec, char *value) +{ + CONFIG_NODE *setnode, *node; + + setnode = iconfig_node_traverse("settings", TRUE); + node = config_node_section(setnode, rec->module, NODE_TYPE_BLOCK); + + iconfig_node_set_str(node, rec->key, value); + iconfig_node_set_str(setnode, rec->key, NULL); + + config_changed = TRUE; +} + +static void settings_clean_invalid_module(const char *module) +{ + CONFIG_NODE *node; + SETTINGS_REC *set; + GSList *tmp, *next; + + node = iconfig_node_traverse("settings", FALSE); + if (node == NULL) return; + + node = config_node_section(node, module, -1); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = next) { + CONFIG_NODE *subnode = tmp->data; + next = tmp->next; + + set = g_hash_table_lookup(settings, subnode->key); + if (set == NULL || strcmp(set->module, module) != 0) + iconfig_node_remove(node, subnode); + } +} + +/* remove all invalid settings from config file. works only with the + modules that have already called settings_check() */ +void settings_clean_invalid(void) +{ + while (last_invalid_modules != NULL) { + char *module = last_invalid_modules->data; + + settings_clean_invalid_module(module); + + g_free(module); + last_invalid_modules = + g_slist_remove(last_invalid_modules, module); + } +} + +/* verify that all settings in config file for `module' are actually found + from /SET list */ +void settings_check_module(const char *module) +{ + SETTINGS_REC *set; + CONFIG_NODE *node; + GString *errors; + GSList *tmp, *next; + int count; + + g_return_if_fail(module != NULL); + + node = iconfig_node_traverse("settings", FALSE); + if (node != NULL) { + /* FIXME: remove after 0.7.98 */ + for (tmp = node->value; tmp != NULL; tmp = next) { + CONFIG_NODE *node = tmp->data; + + next = tmp->next; + if (node->type != NODE_TYPE_KEY) + continue; + set = g_hash_table_lookup(settings, node->key); + if (set != NULL) + settings_move(set, node->value); + } + } + node = node == NULL ? NULL : config_node_section(node, module, -1); + if (node == NULL) return; + + errors = g_string_new(NULL); + g_string_sprintf(errors, "Unknown settings in configuration " + "file for module %s:", module); + + count = 0; + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + set = g_hash_table_lookup(settings, node->key); + if (set == NULL || strcmp(set->module, module) != 0) { + g_string_sprintfa(errors, " %s", node->key); + count++; + } + } + if (count > 0) { + if (gslist_find_icase_string(last_invalid_modules, + module) == NULL) { + /* mark this module having invalid settings */ + last_invalid_modules = + g_slist_append(last_invalid_modules, + g_strdup(module)); + } + if (fe_initialized) + signal_emit("settings errors", 1, errors->str); + else { + if (last_errors == NULL) + last_errors = g_string_new(NULL); + else + g_string_append_c(last_errors, '\n'); + g_string_append(last_errors, errors->str); + } + } + g_string_free(errors, TRUE); +} + +static int settings_compare(SETTINGS_REC *v1, SETTINGS_REC *v2) +{ + return strcmp(v1->section, v2->section); +} + +static void settings_hash_get(const char *key, SETTINGS_REC *rec, + GSList **list) +{ + *list = g_slist_insert_sorted(*list, rec, + (GCompareFunc) settings_compare); +} + +GSList *settings_get_sorted(void) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(settings, (GHFunc) settings_hash_get, &list); + return list; +} + +void sig_term(int n) +{ + /* if we get SIGTERM after this, just die instead of coming back here. */ + signal(SIGTERM, SIG_DFL); + + /* quit from all servers too.. */ + signal_emit("command quit", 1, ""); + + /* and die */ + raise(SIGTERM); +} + +/* Yes, this is my own stupid checksum generator, some "real" algorithm + would be nice but would just take more space without much real benefit */ +static unsigned int file_checksum(const char *fname) +{ + char buf[512]; + int f, ret, n; + unsigned int checksum = 0; + + f = open(fname, O_RDONLY); + if (f == -1) return 0; + + n = 0; + while ((ret = read(f, buf, sizeof(buf))) > 0) { + while (ret-- > 0) + checksum += buf[ret] << ((n++ & 3)*8); + } + close(f); + return checksum; +} + +static void irssi_config_save_state(const char *fname) +{ + struct stat statbuf; + + g_return_if_fail(fname != NULL); + + if (stat(fname, &statbuf) != 0) + return; + + /* save modify time, file size and checksum */ + config_last_mtime = statbuf.st_mtime; + config_last_size = statbuf.st_size; + config_last_checksum = file_checksum(fname); +} + +int irssi_config_is_changed(const char *fname) +{ + struct stat statbuf; + + if (fname == NULL) + fname = mainconfig->fname; + + if (stat(fname, &statbuf) != 0) + return FALSE; + + return config_last_mtime != statbuf.st_mtime && + (config_last_size != statbuf.st_size || + config_last_checksum != file_checksum(fname)); +} + +static CONFIG_REC *parse_configfile(const char *fname) +{ + CONFIG_REC *config; + struct stat statbuf; + const char *path; + char *real_fname; + + real_fname = fname != NULL ? g_strdup(fname) : + g_strdup_printf("%s"G_DIR_SEPARATOR_S".silc" + G_DIR_SEPARATOR_S"config", g_get_home_dir()); + + if (stat(real_fname, &statbuf) == 0) + path = real_fname; + else { + /* user configuration file not found, use the default one + from sysconfdir */ + path = SYSCONFDIR"/irssi/config"; + if (stat(path, &statbuf) != 0) { + /* no configuration file in sysconfdir .. + use the build-in configuration */ + path = NULL; + } + } + + config = config_open(path, -1); + if (config == NULL) { + last_config_error_msg = + g_strdup_printf("Error opening configuration file %s: %s", + path, g_strerror(errno)); + config = config_open(NULL, -1); + } + + if (path != NULL) + config_parse(config); + else + config_parse_data(config, default_config, "internal"); + + config_change_file_name(config, real_fname, 0660); + irssi_config_save_state(real_fname); + g_free(real_fname); + return config; +} + +static void init_configfile(void) +{ + struct stat statbuf; + char *str; + + str = g_strdup_printf("%s"G_DIR_SEPARATOR_S".silc", g_get_home_dir()); + if (stat(str, &statbuf) != 0) { + /* ~/.irssi not found, create it. */ + if (mkpath(str, 0700) != 0) { + g_error("Couldn't create %s directory", str); + } + } else if (!S_ISDIR(statbuf.st_mode)) { + g_error("%s is not a directory.\n" + "You should remove it with command: rm ~/.irssi", str); + } + g_free(str); + + mainconfig = parse_configfile(NULL); + config_last_modifycounter = mainconfig->modifycounter; + + /* any errors? */ + if (config_last_error(mainconfig) != NULL) { + last_config_error_msg = + g_strdup_printf("Ignored errors in configuration " + "file:\n%s", + config_last_error(mainconfig)); + } + + signal(SIGTERM, sig_term); +} + +int settings_reread(const char *fname) +{ + CONFIG_REC *tempconfig; + char *str; + + if (fname == NULL) fname = "~/.silc/config"; + + str = convert_home(fname); + tempconfig = parse_configfile(str); + g_free(str); + + if (tempconfig == NULL) { + signal_emit("gui dialog", 2, "error", g_strerror(errno)); + return FALSE; + } + + if (config_last_error(tempconfig) != NULL) { + str = g_strdup_printf("Errors in configuration file:\n%s", + config_last_error(tempconfig)); + signal_emit("gui dialog", 2, "error", str); + g_free(str); + + config_close(tempconfig); + return FALSE; + } + + config_close(mainconfig); + mainconfig = tempconfig; + config_last_modifycounter = mainconfig->modifycounter; + + signal_emit("setup changed", 0); + signal_emit("setup reread", 0); + return TRUE; +} + +int settings_save(const char *fname) +{ + char *str; + int error; + + if (fname == NULL) + fname = mainconfig->fname; + + error = config_write(mainconfig, fname, 0660) != 0; + irssi_config_save_state(fname); + config_last_modifycounter = mainconfig->modifycounter; + if (error) { + str = g_strdup_printf("Couldn't save configuration file: %s", + config_last_error(mainconfig)); + signal_emit("gui dialog", 2, "error", str); + g_free(str); + } + return !error; +} + +static void sig_autosave(void) +{ + char *fname, *str; + + if (!settings_get_bool("settings_autosave") || + config_last_modifycounter == mainconfig->modifycounter) + return; + + if (!irssi_config_is_changed(NULL)) + settings_save(NULL); + else { + fname = g_strconcat(mainconfig->fname, ".autosave", NULL); + str = g_strdup_printf("Configuration file was modified " + "while irssi was running. Saving " + "configuration to file '%s' instead. " + "Use /SAVE or /RELOAD to get rid of " + "this message.", fname); + signal_emit("gui dialog", 2, "warning", str); + g_free(str); + + settings_save(fname); + g_free(fname); + } +} + +void settings_init(void) +{ + settings = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + + last_errors = NULL; + last_config_error_msg = NULL; + last_invalid_modules = NULL; + fe_initialized = FALSE; + config_changed = FALSE; + + config_last_mtime = 0; + config_last_modifycounter = 0; + init_configfile(); + + settings_add_bool("misc", "settings_autosave", TRUE); + timeout_tag = g_timeout_add(1000*60*60, (GSourceFunc) sig_autosave, NULL); + signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished); + signal_add("gui exit", (SIGNAL_FUNC) sig_autosave); +} + +static void settings_hash_free(const char *key, SETTINGS_REC *rec) +{ + settings_destroy(rec); +} + +void settings_deinit(void) +{ + g_source_remove(timeout_tag); + signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished); + signal_remove("gui exit", (SIGNAL_FUNC) sig_autosave); + + g_slist_foreach(last_invalid_modules, (GFunc) g_free, NULL); + g_slist_free(last_invalid_modules); + + g_hash_table_foreach(settings, (GHFunc) settings_hash_free, NULL); + g_hash_table_destroy(settings); + + if (mainconfig != NULL) config_close(mainconfig); +} diff --git a/apps/irssi/src/core/settings.h b/apps/irssi/src/core/settings.h new file mode 100644 index 00000000..cea8c4d0 --- /dev/null +++ b/apps/irssi/src/core/settings.h @@ -0,0 +1,89 @@ +#ifndef __SETTINGS_H +#define __SETTINGS_H + +enum { + SETTING_TYPE_STRING, + SETTING_TYPE_INT, + SETTING_TYPE_BOOLEAN +}; + +typedef struct { + char *module; + int type; + char *key; + char *section; + void *def; +} SETTINGS_REC; + +/* macros for handling the default Irssi configuration */ +#define iconfig_get_str(a, b, c) config_get_str(mainconfig, a, b, c) +#define iconfig_get_int(a, b, c) config_get_int(mainconfig, a, b, c) +#define iconfig_get_bool(a, b, c) config_get_bool(mainconfig, a, b, c) +#define iconfig_list_find(a, b, c, d) config_list_find(mainconfig, a, b, c, d) + +#define iconfig_set_str(a, b, c) config_set_str(mainconfig, a, b, c) +#define iconfig_set_int(a, b, c) config_set_int(mainconfig, a, b, c) +#define iconfig_set_bool(a, b, c) config_set_bool(mainconfig, a, b, c) + +#define iconfig_node_traverse(a, b) config_node_traverse(mainconfig, a, b) +#define iconfig_node_set_str(a, b, c) config_node_set_str(mainconfig, a, b, c) +#define iconfig_node_set_int(a, b, c) config_node_set_int(mainconfig, a, b, c) +#define iconfig_node_set_bool(a, b, c) config_node_set_bool(mainconfig, a, b, c) +#define iconfig_node_list_remove(a, b) config_node_list_remove(mainconfig, a, b) +#define iconfig_node_remove(a, b) config_node_remove(mainconfig, a, b) +#define iconfig_node_clear(a) config_node_clear(mainconfig, a) +#define iconfig_node_add_list(a, b) config_node_add_list(mainconfig, a, b) + +extern CONFIG_REC *mainconfig; + +/* Functions for handling the "settings" node of Irssi configuration */ +const char *settings_get_str(const char *key); +int settings_get_int(const char *key); +int settings_get_bool(const char *key); + +/* Functions to add/remove settings */ +void settings_add_str_module(const char *module, const char *section, + const char *key, const char *def); +void settings_add_int_module(const char *module, const char *section, + const char *key, int def); +void settings_add_bool_module(const char *module, const char *section, + const char *key, int def); +void settings_remove(const char *key); +void settings_remove_module(const char *module); + +#define settings_add_str(section, key, def) \ + settings_add_str_module(MODULE_NAME, section, key, def) +#define settings_add_int(section, key, def) \ + settings_add_int_module(MODULE_NAME, section, key, def) +#define settings_add_bool(section, key, def) \ + settings_add_bool_module(MODULE_NAME, section, key, def) + +void settings_set_str(const char *key, const char *value); +void settings_set_int(const char *key, int value); +void settings_set_bool(const char *key, int value); + +/* Get the type (SETTING_TYPE_xxx) of `key' */ +int settings_get_type(const char *key); +/* Get all settings sorted by section. Free the result with g_slist_free() */ +GSList *settings_get_sorted(void); +/* Get the record of the setting */ +SETTINGS_REC *settings_get_record(const char *key); + +/* verify that all settings in config file for `module' are actually found + from /SET list */ +void settings_check_module(const char *module); +#define settings_check() settings_check_module(MODULE_NAME) + +/* remove all invalid settings from config file. works only with the + modules that have already called settings_check() */ +void settings_clean_invalid(void); + +/* if `fname' is NULL, the default is used */ +int settings_reread(const char *fname); +int settings_save(const char *fname); +int irssi_config_is_changed(const char *fname); + +void settings_init(void); +void settings_deinit(void); + +#endif diff --git a/apps/irssi/src/core/signals.c b/apps/irssi/src/core/signals.c new file mode 100644 index 00000000..cb964eb0 --- /dev/null +++ b/apps/irssi/src/core/signals.c @@ -0,0 +1,388 @@ +/* + signals.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "../common.h" +#include "signals.h" +#include "modules.h" + +#define SIGNAL_LISTS 3 + +typedef struct { + int id; /* signal id */ + + int emitting; /* signal is being emitted */ + int altered; /* some signal functions are marked as NULL */ + int stop_emit; /* this signal was stopped */ + + GPtrArray *modulelist[SIGNAL_LISTS]; /* list of what signals belong + to which module */ + GPtrArray *siglist[SIGNAL_LISTS]; /* signal lists */ +} SIGNAL_REC; + +#define signal_is_emitlist_empty(a) \ + (!(a)->siglist[0] && !(a)->siglist[1] && !(a)->siglist[2]) + +static GMemChunk *signals_chunk; +static GHashTable *signals; +static SIGNAL_REC *current_emitted_signal; + +void signal_add_to(const char *module, int pos, + const char *signal, SIGNAL_FUNC func) +{ + g_return_if_fail(signal != NULL); + + signal_add_to_id(module, pos, signal_get_uniq_id(signal), func); +} + +/* bind a signal */ +void signal_add_to_id(const char *module, int pos, + int signal_id, SIGNAL_FUNC func) +{ + SIGNAL_REC *rec; + + g_return_if_fail(signal_id >= 0); + g_return_if_fail(func != NULL); + g_return_if_fail(pos >= 0 && pos < SIGNAL_LISTS); + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec == NULL) { + rec = g_mem_chunk_alloc0(signals_chunk); + rec->id = signal_id; + g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), rec); + } + + if (rec->siglist[pos] == NULL) { + rec->siglist[pos] = g_ptr_array_new(); + rec->modulelist[pos] = g_ptr_array_new(); + } + + g_ptr_array_add(rec->siglist[pos], (void *) func); + g_ptr_array_add(rec->modulelist[pos], (void *) module); +} + +/* Destroy the whole signal */ +static void signal_destroy(int signal_id) +{ + SIGNAL_REC *rec; + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec != NULL) { + /* remove whole signal from memory */ + g_hash_table_remove(signals, GINT_TO_POINTER(signal_id)); + g_free(rec); + } +} + +static int signal_list_find(GPtrArray *array, void *data) +{ + unsigned int n; + + for (n = 0; n < array->len; n++) { + if (g_ptr_array_index(array, n) == data) + return n; + } + + return -1; +} + +static void signal_remove_from_list(SIGNAL_REC *rec, int signal_id, + int list, int index) +{ + if (rec->emitting) { + g_ptr_array_index(rec->siglist[list], index) = NULL; + rec->altered = TRUE; + } else { + g_ptr_array_remove_index(rec->siglist[list], index); + g_ptr_array_remove_index(rec->modulelist[list], index); + if (signal_is_emitlist_empty(rec)) + signal_destroy(signal_id); + } +} + +/* Remove signal from emit lists */ +static int signal_remove_from_lists(SIGNAL_REC *rec, int signal_id, + SIGNAL_FUNC func) +{ + int n, index; + + for (n = 0; n < SIGNAL_LISTS; n++) { + if (rec->siglist[n] == NULL) + continue; + + index = signal_list_find(rec->siglist[n], (void *) func); + if (index != -1) { + /* remove the function from emit list */ + signal_remove_from_list(rec, signal_id, n, index); + return 1; + } + } + + return 0; +} + +void signal_remove_id(int signal_id, SIGNAL_FUNC func) +{ + SIGNAL_REC *rec; + + g_return_if_fail(signal_id >= 0); + g_return_if_fail(func != NULL); + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec != NULL) + signal_remove_from_lists(rec, signal_id, func); +} + +/* unbind signal */ +void signal_remove(const char *signal, SIGNAL_FUNC func) +{ + g_return_if_fail(signal != NULL); + + signal_remove_id(signal_get_uniq_id(signal), func); +} + +/* Remove all NULL functions from signal list */ +static void signal_list_clean(SIGNAL_REC *rec) +{ + int n, index; + + for (n = 0; n < SIGNAL_LISTS; n++) { + if (rec->siglist[n] == NULL) + continue; + + for (index = rec->siglist[n]->len-1; index >= 0; index--) { + if (g_ptr_array_index(rec->siglist[n], index) == NULL) { + g_ptr_array_remove_index(rec->siglist[n], index); + g_ptr_array_remove_index(rec->modulelist[n], index); + } + } + } +} + +static int signal_emit_real(SIGNAL_REC *rec, gconstpointer *arglist) +{ + SIGNAL_REC *prev_emitted_signal; + SIGNAL_FUNC func; + int n, index, stopped, stop_emit_count; + + /* signal_stop_by_name("signal"); signal_emit("signal", ...); + fails if we compare rec->stop_emit against 0. */ + stop_emit_count = rec->stop_emit; + + stopped = FALSE; + rec->emitting++; + for (n = 0; n < SIGNAL_LISTS; n++) { + /* run signals in emit lists */ + if (rec->siglist[n] == NULL) + continue; + + for (index = rec->siglist[n]->len-1; index >= 0; index--) { + func = (SIGNAL_FUNC) g_ptr_array_index(rec->siglist[n], index); + + if (func != NULL) { + prev_emitted_signal = current_emitted_signal; + current_emitted_signal = rec; +#if SIGNAL_MAX_ARGUMENTS != 6 +# error SIGNAL_MAX_ARGS changed - update code +#endif + func(arglist[0], arglist[1], arglist[2], arglist[3], arglist[4], arglist[5]); + current_emitted_signal = prev_emitted_signal; + } + + if (rec->stop_emit != stop_emit_count) { + stopped = TRUE; + rec->stop_emit--; + n = SIGNAL_LISTS; + break; + } + } + } + rec->emitting--; + + if (!rec->emitting) { + if (rec->stop_emit != 0) { + /* signal_stop() used too many times */ + rec->stop_emit = 0; + } + if (rec->altered) { + signal_list_clean(rec); + rec->altered = FALSE; + } + } + + return stopped; +} + +static int signal_emitv_id(int signal_id, int params, va_list va) +{ + gconstpointer arglist[SIGNAL_MAX_ARGUMENTS]; + SIGNAL_REC *rec; + int n; + + g_return_val_if_fail(signal_id >= 0, FALSE); + g_return_val_if_fail(params >= 0 && params <= SIGNAL_MAX_ARGUMENTS, FALSE); + + for (n = 0; n < SIGNAL_MAX_ARGUMENTS; n++) + arglist[n] = n >= params ? NULL : va_arg(va, gconstpointer); + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec != NULL && signal_emit_real(rec, arglist)) + return TRUE; + + return rec != NULL; +} + +int signal_emit(const char *signal, int params, ...) +{ + va_list va; + int signal_id, ret; + + /* get arguments */ + signal_id = signal_get_uniq_id(signal); + + va_start(va, params); + ret = signal_emitv_id(signal_id, params, va); + va_end(va); + + return ret; +} + +int signal_emit_id(int signal_id, int params, ...) +{ + va_list va; + int ret; + + /* get arguments */ + va_start(va, params); + ret = signal_emitv_id(signal_id, params, va); + va_end(va); + + return ret; +} + +/* stop the current ongoing signal emission */ +void signal_stop(void) +{ + SIGNAL_REC *rec; + + rec = current_emitted_signal; + if (rec == NULL) + g_warning("signal_stop() : no signals are being emitted currently"); + else if (rec->emitting > rec->stop_emit) + rec->stop_emit++; +} + +/* stop ongoing signal emission by signal name */ +void signal_stop_by_name(const char *signal) +{ + SIGNAL_REC *rec; + int signal_id; + + signal_id = signal_get_uniq_id(signal); + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + if (rec == NULL) + g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal); + else if (rec->emitting > rec->stop_emit) + rec->stop_emit++; +} + +/* return the name of the signal that is currently being emitted */ +const char *signal_get_emitted(void) +{ + return signal_get_id_str(signal_get_emitted_id()); +} + +/* return the ID of the signal that is currently being emitted */ +int signal_get_emitted_id(void) +{ + SIGNAL_REC *rec; + + rec = current_emitted_signal; + g_return_val_if_fail(rec != NULL, -1); + return rec->id; +} + +/* return TRUE if specified signal was stopped */ +int signal_is_stopped(int signal_id) +{ + SIGNAL_REC *rec; + + rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id)); + g_return_val_if_fail(rec != NULL, FALSE); + + return rec->emitting <= rec->stop_emit; +} + +static void signal_remove_module(void *signal, SIGNAL_REC *rec, + const char *module) +{ + unsigned int index; + int signal_id, list; + + signal_id = GPOINTER_TO_INT(signal); + + for (list = 0; list < SIGNAL_LISTS; list++) { + if (rec->modulelist[list] == NULL) + continue; + + for (index = 0; index < rec->modulelist[list]->len; index++) { + if (g_strcasecmp(g_ptr_array_index(rec->modulelist[list], index), module) == 0) + signal_remove_from_list(rec, signal_id, list, index); + } + } +} + +/* remove all signals that belong to `module' */ +void signals_remove_module(const char *module) +{ + g_return_if_fail(module != NULL); + + g_hash_table_foreach(signals, (GHFunc) signal_remove_module, (void *) module); +} + +void signals_init(void) +{ + signals_chunk = g_mem_chunk_new("signals", sizeof(SIGNAL_REC), + sizeof(SIGNAL_REC)*200, G_ALLOC_AND_FREE); + signals = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal); +} + +static void signal_free(void *key, SIGNAL_REC *rec) +{ + int n; + + for (n = 0; n < SIGNAL_LISTS; n++) { + if (rec->siglist[n] != NULL) { + g_ptr_array_free(rec->siglist[n], TRUE); + g_ptr_array_free(rec->modulelist[n], TRUE); + } + } + + g_mem_chunk_free(signals_chunk, rec); + current_emitted_signal = NULL; +} + +void signals_deinit(void) +{ + g_hash_table_foreach(signals, (GHFunc) signal_free, NULL); + g_hash_table_destroy(signals); + + module_uniq_destroy("signals"); + g_mem_chunk_destroy(signals_chunk); +} diff --git a/apps/irssi/src/core/signals.h b/apps/irssi/src/core/signals.h new file mode 100644 index 00000000..795f7327 --- /dev/null +++ b/apps/irssi/src/core/signals.h @@ -0,0 +1,51 @@ +#ifndef __SIGNAL_H +#define __SIGNAL_H + +#define SIGNAL_MAX_ARGUMENTS 6 +typedef void (*SIGNAL_FUNC) (gconstpointer, gconstpointer, + gconstpointer, gconstpointer, + gconstpointer, gconstpointer); + +void signals_init(void); +void signals_deinit(void); + +/* signal name -> ID */ +#define signal_get_uniq_id(signal) \ + module_get_uniq_id_str("signals", signal) +/* signal ID -> name */ +#define signal_get_id_str(signal_id) \ + module_find_id_str("signals", signal_id) + +/* bind a signal */ +void signal_add_to(const char *module, int pos, + const char *signal, SIGNAL_FUNC func); +void signal_add_to_id(const char *module, int pos, + int signal, SIGNAL_FUNC func); +#define signal_add(a, b) signal_add_to(MODULE_NAME, 1, a, b) +#define signal_add_first(a, b) signal_add_to(MODULE_NAME, 0, a, b) +#define signal_add_last(a, b) signal_add_to(MODULE_NAME, 2, a, b) + +/* unbind signal */ +void signal_remove(const char *signal, SIGNAL_FUNC func); +void signal_remove_id(int signal_id, SIGNAL_FUNC func); + +/* emit signal */ +int signal_emit(const char *signal, int params, ...); +int signal_emit_id(int signal_id, int params, ...); + +/* stop the current ongoing signal emission */ +void signal_stop(void); +/* stop ongoing signal emission by signal name */ +void signal_stop_by_name(const char *signal); + +/* return the name of the signal that is currently being emitted */ +const char *signal_get_emitted(void); +/* return the ID of the signal that is currently being emitted */ +int signal_get_emitted_id(void); +/* return TRUE if specified signal was stopped */ +int signal_is_stopped(int signal_id); + +/* remove all signals that belong to `module' */ +void signals_remove_module(const char *module); + +#endif diff --git a/apps/irssi/src/core/special-vars.c b/apps/irssi/src/core/special-vars.c new file mode 100644 index 00000000..40372f93 --- /dev/null +++ b/apps/irssi/src/core/special-vars.c @@ -0,0 +1,611 @@ +/* + special-vars.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "special-vars.h" +#include "expandos.h" +#include "settings.h" +#include "misc.h" + +#define ALIGN_RIGHT 0x01 +#define ALIGN_CUT 0x02 +#define ALIGN_PAD 0x04 + +#define isvarchar(c) \ + (isalnum(c) || (c) == '_') + +static SPECIAL_HISTORY_FUNC history_func = NULL; + +static char *get_argument(char **cmd, char **arglist) +{ + GString *str; + char *ret; + int max, arg, argcount; + + arg = 0; + max = -1; + + argcount = strarray_length(arglist); + + if (**cmd == '*') { + /* get all arguments */ + } else if (**cmd == '~') { + /* get last argument */ + arg = max = argcount-1; + } else { + if (isdigit(**cmd)) { + /* first argument */ + arg = max = (**cmd)-'0'; + (*cmd)++; + } + + if (**cmd == '-') { + /* get more than one argument */ + (*cmd)++; + if (!isdigit(**cmd)) + max = -1; /* get all the rest */ + else { + max = (**cmd)-'0'; + (*cmd)++; + } + } + (*cmd)--; + } + + str = g_string_new(NULL); + while (arg < argcount && (arg <= max || max == -1)) { + g_string_append(str, arglist[arg]); + g_string_append_c(str, ' '); + arg++; + } + if (str->len > 0) g_string_truncate(str, str->len-1); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +static char *get_internal_setting(const char *key, int type, int *free_ret) +{ + switch (type) { + case SETTING_TYPE_BOOLEAN: + return settings_get_bool(key) ? "yes" : "no"; + case SETTING_TYPE_INT: + *free_ret = TRUE; + return g_strdup_printf("%d", settings_get_int(key)); + case SETTING_TYPE_STRING: + return (char *) settings_get_str(key); + } + + return NULL; +} + +static char *get_long_variable_value(const char *key, SERVER_REC *server, + void *item, int *free_ret) +{ + EXPANDO_FUNC func; + char *ret; + int type; + + *free_ret = FALSE; + + /* expando? */ + func = expando_find_long(key); + if (func != NULL) + return func(server, item, free_ret); + + /* internal setting? */ + type = settings_get_type(key); + if (type != -1) + return get_internal_setting(key, type, free_ret); + + /* environment variable? */ + ret = g_getenv(key); + if (ret != NULL) + return ret; + + return NULL; +} + +static char *get_long_variable(char **cmd, SERVER_REC *server, + void *item, int *free_ret, int getname) +{ + char *start, *var, *ret; + + /* get variable name */ + start = *cmd; + while (isvarchar((*cmd)[1])) (*cmd)++; + + var = g_strndup(start, (int) (*cmd-start)+1); + if (getname) { + *free_ret = TRUE; + return var; + } + ret = get_long_variable_value(var, server, item, free_ret); + g_free(var); + return ret; +} + +/* return the value of the variable found from `cmd'. + if 'getname' is TRUE, return the name of the variable instead it's value */ +static char *get_variable(char **cmd, SERVER_REC *server, void *item, + char **arglist, int *free_ret, int *arg_used, + int getname) +{ + EXPANDO_FUNC func; + + if (isdigit(**cmd) || **cmd == '*' || **cmd == '-' || **cmd == '~') { + /* argument */ + *free_ret = TRUE; + if (arg_used != NULL) *arg_used = TRUE; + return getname ? g_strdup_printf("%c", **cmd) : + get_argument(cmd, arglist); + } + + if (isalpha(**cmd) && isvarchar((*cmd)[1])) { + /* long variable name.. */ + return get_long_variable(cmd, server, item, free_ret, getname); + } + + /* single character variable. */ + if (getname) { + *free_ret = TRUE; + return g_strdup_printf("%c", **cmd); + } + *free_ret = FALSE; + func = expando_find_char(**cmd); + return func == NULL ? NULL : func(server, item, free_ret); +} + +static char *get_history(char **cmd, void *item, int *free_ret) +{ + char *start, *text, *ret; + + /* get variable name */ + start = ++(*cmd); + while (**cmd != '\0' && **cmd != '!') (*cmd)++; + + if (history_func == NULL) + ret = NULL; + else { + text = g_strndup(start, (int) (*cmd-start)+1); + ret = history_func(text, item, free_ret); + g_free(text); + } + + if (**cmd == '\0') (*cmd)--; + return ret; +} + +static char *get_special_value(char **cmd, SERVER_REC *server, void *item, + char **arglist, int *free_ret, int *arg_used, + int flags) +{ + char command, *value, *p; + int len; + + if (**cmd == '!') { + /* find text from command history */ + if (flags & PARSE_FLAG_GETNAME) + return "!"; + + return get_history(cmd, item, free_ret); + } + + command = 0; + if (**cmd == '#' || **cmd == '@') { + command = **cmd; + if ((*cmd)[1] != '\0') + (*cmd)++; + else { + /* default to $* */ + char *temp_cmd = "*"; + + if (flags & PARSE_FLAG_GETNAME) + return "*"; + + *free_ret = TRUE; + return get_argument(&temp_cmd, arglist); + } + } + + value = get_variable(cmd, server, item, arglist, free_ret, + arg_used, flags & PARSE_FLAG_GETNAME); + + if (flags & PARSE_FLAG_GETNAME) + return value; + + if (command == '#') { + /* number of words */ + if (value == NULL || *value == '\0') { + if (value != NULL && *free_ret) { + g_free(value); + *free_ret = FALSE; + } + return "0"; + } + + len = 1; + for (p = value; *p != '\0'; p++) { + if (*p == ' ' && (p[1] != ' ' && p[1] != '\0')) + len++; + } + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return g_strdup_printf("%d", len); + } + + if (command == '@') { + /* number of characters */ + if (value == NULL) return "0"; + + len = strlen(value); + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return g_strdup_printf("%d", len); + } + + return value; +} + +/* get alignment arguments (inside the []) */ +static int get_alignment_args(char **data, int *align, int *flags, char *pad) +{ + char *str; + + *align = 0; + *flags = ALIGN_CUT|ALIGN_PAD; + *pad = ' '; + + /* '!' = don't cut, '-' = right padding */ + str = *data; + while (*str != '\0' && *str != ']' && !isdigit(*str)) { + if (*str == '!') + *flags &= ~ALIGN_CUT; + else if (*str == '-') + *flags |= ALIGN_RIGHT; + else if (*str == '.') + *flags &= ~ALIGN_PAD; + str++; + } + if (!isdigit(*str)) + return FALSE; /* expecting number */ + + /* get the alignment size */ + while (isdigit(*str)) { + *align = (*align) * 10 + (*str-'0'); + str++; + } + + /* get the pad character */ + while (*str != '\0' && *str != ']') { + *pad = *str; + str++; + } + + if (*str++ != ']') return FALSE; + + *data = str; + return TRUE; +} + +/* return the aligned text */ +static char *get_alignment(const char *text, int align, int flags, char pad) +{ + GString *str; + char *ret; + + g_return_val_if_fail(text != NULL, NULL); + + str = g_string_new(text); + + /* cut */ + if ((flags & ALIGN_CUT) && align > 0 && str->len > align) + g_string_truncate(str, align); + + /* add pad characters */ + if (flags & ALIGN_PAD) { + while (str->len < align) { + if (flags & ALIGN_RIGHT) + g_string_prepend_c(str, pad); + else + g_string_append_c(str, pad); + } + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +/* Parse and expand text after '$' character. return value has to be + g_free()'d if `free_ret' is TRUE. */ +char *parse_special(char **cmd, SERVER_REC *server, void *item, + char **arglist, int *free_ret, int *arg_used, int flags) +{ + static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */ + char command, *value; + + char align_pad; + int align, align_flags; + + char *nest_value; + int brackets, nest_free; + + *free_ret = FALSE; + + command = **cmd; (*cmd)++; + switch (command) { + case '[': + /* alignment */ + if (!get_alignment_args(cmd, &align, &align_flags, + &align_pad) || **cmd == '\0') { + (*cmd)--; + return NULL; + } + break; + default: + command = 0; + (*cmd)--; + } + + nest_free = FALSE; nest_value = NULL; + if (**cmd == '(') { + /* subvariable */ + int toplevel = nested_orig_cmd == NULL; + + if (toplevel) nested_orig_cmd = cmd; + (*cmd)++; + if (**cmd != '$') { + /* ... */ + nest_value = *cmd; + } else { + (*cmd)++; + nest_value = parse_special(cmd, server, item, arglist, + &nest_free, arg_used, + flags); + } + + while ((*nested_orig_cmd)[1] != '\0') { + (*nested_orig_cmd)++; + if (**nested_orig_cmd == ')') + break; + } + cmd = &nest_value; + + if (toplevel) nested_orig_cmd = NULL; + } + + if (**cmd != '{') + brackets = FALSE; + else { + /* special value is inside {...} (foo${test}bar -> fooXXXbar) */ + (*cmd)++; + brackets = TRUE; + } + + value = get_special_value(cmd, server, item, arglist, + free_ret, arg_used, flags); + if (**cmd == '\0') + g_error("parse_special() : buffer overflow!"); + + if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY)) + *arg_used = TRUE; + + if (brackets) { + while (**cmd != '}' && (*cmd)[1] != '\0') + (*cmd)++; + } + + if (nest_free) g_free(nest_value); + + if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) { + /* alignment */ + char *p; + + if (value == NULL) return ""; + + p = get_alignment(value, align, align_flags, align_pad); + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return p; + } + + return value; +} + +static void gstring_append_escaped(GString *str, const char *text) +{ + while (*text != '\0') { + if (*text == '%') + g_string_append_c(str, '%'); + g_string_append_c(str, *text); + text++; + } +} + +/* parse the whole string. $ and \ chars are replaced */ +char *parse_special_string(const char *cmd, SERVER_REC *server, void *item, + const char *data, int *arg_used, int flags) +{ + char code, **arglist, *ret; + GString *str; + int need_free; + + g_return_val_if_fail(cmd != NULL, NULL); + g_return_val_if_fail(data != NULL, NULL); + + /* create the argument list */ + arglist = g_strsplit(data, " ", -1); + + if (arg_used != NULL) *arg_used = FALSE; + code = 0; + str = g_string_new(NULL); + while (*cmd != '\0') { + if (code == '\\'){ + switch (*cmd) { + case 't': + g_string_append_c(str, '\t'); + break; + case 'n': + g_string_append_c(str, '\n'); + break; + default: + g_string_append_c(str, *cmd); + } + code = 0; + } else if (code == '$') { + char *ret; + + ret = parse_special((char **) &cmd, server, item, + arglist, &need_free, arg_used, + flags); + if (ret != NULL) { + if ((flags & PARSE_FLAG_ESCAPE_VARS) == 0) + g_string_append(str, ret); + else + gstring_append_escaped(str, ret); + if (need_free) g_free(ret); + } + code = 0; + } else { + if (*cmd == '\\' || *cmd == '$') + code = *cmd; + else + g_string_append_c(str, *cmd); + } + + cmd++; + } + g_strfreev(arglist); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +#define is_split_char(str, start) \ + ((str)[0] == ';' && ((start) == (str) || \ + ((str)[-1] != '\\' && (str)[-1] != '$'))) + +/* execute the commands in string - commands can be split with ';' */ +void eval_special_string(const char *cmd, const char *data, + SERVER_REC *server, void *item) +{ + const char *cmdchars; + char *orig, *str, *start, *ret; + int arg_used, arg_used_ever; + GSList *commands; + + commands = NULL; + arg_used_ever = FALSE; + cmdchars = settings_get_str("cmdchars"); + + /* get a list of all the commands to run */ + orig = start = str = g_strdup(cmd); + do { + if (is_split_char(str, start)) + *str++ = '\0'; + else if (*str != '\0') { + str++; + continue; + } + + ret = parse_special_string(start, server, item, + data, &arg_used, 0); + if (arg_used) arg_used_ever = TRUE; + + if (strchr(cmdchars, *ret) == NULL) { + /* no command char - let's put it there.. */ + char *old = ret; + + ret = g_strdup_printf("%c%s", *cmdchars, old); + g_free(old); + } + commands = g_slist_append(commands, ret); + start = str; + } while (*start != '\0'); + + /* run the command, if no arguments were ever used, append all of them + after each command */ + while (commands != NULL) { + ret = commands->data; + + if (!arg_used_ever && *data != '\0') { + char *old = ret; + + ret = g_strconcat(old, " ", data, NULL); + g_free(old); + } + signal_emit("send command", 3, ret, server, item); + + g_free(ret); + commands = g_slist_remove(commands, commands->data); + } + g_free(orig); +} + +void special_history_func_set(SPECIAL_HISTORY_FUNC func) +{ + history_func = func; +} + +static void special_vars_signals_do(const char *text, int funccount, + SIGNAL_FUNC *funcs, int bind) +{ + char *ret; + int need_free; + + while (*text != '\0') { + if (*text == '\\' && text[1] != '\0') { + text += 2; + } else if (*text == '$' && text[1] != '\0') { + text++; + ret = parse_special((char **) &text, NULL, NULL, + NULL, &need_free, NULL, + PARSE_FLAG_GETNAME); + if (ret != NULL) { + if (bind) + expando_bind(ret, funccount, funcs); + else + expando_unbind(ret, funccount, funcs); + if (need_free) g_free(ret); + } + + } + else text++; + } +} + +void special_vars_add_signals(const char *text, + int funccount, SIGNAL_FUNC *funcs) +{ + special_vars_signals_do(text, funccount, funcs, TRUE); +} + +void special_vars_remove_signals(const char *text, + int funccount, SIGNAL_FUNC *funcs) +{ + special_vars_signals_do(text, funccount, funcs, FALSE); +} diff --git a/apps/irssi/src/core/special-vars.h b/apps/irssi/src/core/special-vars.h new file mode 100644 index 00000000..af02e121 --- /dev/null +++ b/apps/irssi/src/core/special-vars.h @@ -0,0 +1,33 @@ +#ifndef __SPECIAL_VARS_H +#define __SPECIAL_VARS_H + +#include "signals.h" + +#define PARSE_FLAG_GETNAME 0x01 /* return argument name instead of it's value */ +#define PARSE_FLAG_ISSET_ANY 0x02 /* arg_used field specifies that at least one of the $variables was non-empty */ +#define PARSE_FLAG_ESCAPE_VARS 0x04 /* if any arguments/variables contain % chars, escape them with another % */ + +typedef char* (*SPECIAL_HISTORY_FUNC) + (const char *text, void *item, int *free_ret); + +/* Parse and expand text after '$' character. return value has to be + g_free()'d if `free_ret' is TRUE. */ +char *parse_special(char **cmd, SERVER_REC *server, void *item, + char **arglist, int *free_ret, int *arg_used, int flags); + +/* parse the whole string. $ and \ chars are replaced */ +char *parse_special_string(const char *cmd, SERVER_REC *server, void *item, + const char *data, int *arg_used, int flags); + +/* execute the commands in string - commands can be split with ';' */ +void eval_special_string(const char *cmd, const char *data, + SERVER_REC *server, void *item); + +void special_history_func_set(SPECIAL_HISTORY_FUNC func); + +void special_vars_add_signals(const char *text, + int funccount, SIGNAL_FUNC *funcs); +void special_vars_remove_signals(const char *text, + int funccount, SIGNAL_FUNC *funcs); + +#endif diff --git a/apps/irssi/src/core/window-item-def.h b/apps/irssi/src/core/window-item-def.h new file mode 100644 index 00000000..4364c66e --- /dev/null +++ b/apps/irssi/src/core/window-item-def.h @@ -0,0 +1,9 @@ +#ifndef __WINDOW_ITEM_DEF_H +#define __WINDOW_ITEM_DEF_H + +#define STRUCT_SERVER_REC SERVER_REC +struct _WI_ITEM_REC { +#include "window-item-rec.h" +}; + +#endif diff --git a/apps/irssi/src/core/window-item-rec.h b/apps/irssi/src/core/window-item-rec.h new file mode 100644 index 00000000..5c09a5b0 --- /dev/null +++ b/apps/irssi/src/core/window-item-rec.h @@ -0,0 +1,15 @@ +/* WI_ITEM_REC definition, used for inheritance */ + +int type; /* module_get_uniq_id("CHANNEL/QUERY/xxx", 0) */ +int chat_type; /* chat_protocol_lookup(xx) */ +GHashTable *module_data; + +void *window; +STRUCT_SERVER_REC *server; +char *name; + +time_t createtime; +int data_level; +char *hilight_color; + +#undef STRUCT_SERVER_REC diff --git a/apps/irssi/src/core/write-buffer.c b/apps/irssi/src/core/write-buffer.c new file mode 100644 index 00000000..762fc24e --- /dev/null +++ b/apps/irssi/src/core/write-buffer.c @@ -0,0 +1,184 @@ +/* + write-buffer.c : irssi + + Copyright (C) 2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "settings.h" +#include "write-buffer.h" + +#define BUFFER_BLOCK_SIZE 2048 + +typedef struct { + char *active_block; + int active_block_pos; + + GSList *blocks; +} BUFFER_REC; + +static GSList *empty_blocks; +static GHashTable *buffers; +static int block_count; + +static int write_buffer_max_blocks; +static int timeout_tag; + +static void write_buffer_new_block(BUFFER_REC *rec) +{ + char *block; + + if (empty_blocks == NULL) + block = g_malloc(BUFFER_BLOCK_SIZE); + else { + block = empty_blocks->data; + empty_blocks = g_slist_remove(empty_blocks, block); + } + + block_count++; + rec->active_block = block; + rec->active_block_pos = 0; + rec->blocks = g_slist_append(rec->blocks, block); +} + +int write_buffer(int handle, const void *data, int size) +{ + BUFFER_REC *rec; + const char *cdata = data; + int next_size; + + if (write_buffer_max_blocks <= 0) { + /* no write buffer */ + return write(handle, data, size); + } + + if (size <= 0) + return size; + + rec = g_hash_table_lookup(buffers, GINT_TO_POINTER(handle)); + if (rec == NULL) { + rec = g_new0(BUFFER_REC, 1); + write_buffer_new_block(rec); + g_hash_table_insert(buffers, GINT_TO_POINTER(handle), rec); + } + + while (size > 0) { + if (rec->active_block_pos == BUFFER_BLOCK_SIZE) + write_buffer_new_block(rec); + + next_size = size < BUFFER_BLOCK_SIZE-rec->active_block_pos ? + size : BUFFER_BLOCK_SIZE-rec->active_block_pos; + memcpy(rec->active_block+rec->active_block_pos, + cdata, next_size); + + rec->active_block_pos += next_size; + cdata += next_size; + size -= next_size; + } + + if (block_count > write_buffer_max_blocks) + write_buffer_flush(); + + return size; +} + +static int write_buffer_flush_rec(void *handlep, BUFFER_REC *rec) +{ + GSList *tmp; + int handle, size; + + handle = GPOINTER_TO_INT(handlep); + for (tmp = rec->blocks; tmp != NULL; tmp = tmp->next) { + size = tmp->data != rec->active_block ? BUFFER_BLOCK_SIZE : + rec->active_block_pos; + write(handle, tmp->data, size); + } + + empty_blocks = g_slist_concat(empty_blocks, rec->blocks); + g_free(rec); + return TRUE; +} + +void write_buffer_flush(void) +{ + g_slist_foreach(empty_blocks, (GFunc) g_free, NULL); + g_slist_free(empty_blocks); + empty_blocks = NULL; + + g_hash_table_foreach_remove(buffers, + (GHRFunc) write_buffer_flush_rec, NULL); + block_count = 0; +} + +static void read_settings(void) +{ + int msecs; + + if (timeout_tag != -1) + g_source_remove(timeout_tag); + + write_buffer_flush(); + + write_buffer_max_blocks = settings_get_int("write_buffer_kb") * + 1024/BUFFER_BLOCK_SIZE; + + if (settings_get_int("write_buffer_mins") > 0) { + msecs = settings_get_int("write_buffer_mins")*60*1000; + timeout_tag = g_timeout_add(msecs, + (GSourceFunc) write_buffer_flush, + NULL); + } +} + +static void cmd_flushbuffer(void) +{ + write_buffer_flush(); +} + +void write_buffer_init(void) +{ + settings_add_int("misc", "write_buffer_mins", 0); + settings_add_int("misc", "write_buffer_kb", 0); + + buffers = g_hash_table_new((GHashFunc) g_direct_hash, + (GCompareFunc) g_direct_equal); + + empty_blocks = NULL; + block_count = 0; + + timeout_tag = -1; + read_settings(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + command_bind("flushbuffer", NULL, (SIGNAL_FUNC) cmd_flushbuffer); +} + +void write_buffer_deinit(void) +{ + if (timeout_tag != -1) + g_source_remove(timeout_tag); + + write_buffer_flush(); + g_hash_table_destroy(buffers); + + g_slist_foreach(empty_blocks, (GFunc) g_free, NULL); + g_slist_free(empty_blocks); + + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + command_unbind("flushbuffer", (SIGNAL_FUNC) cmd_flushbuffer); +} diff --git a/apps/irssi/src/core/write-buffer.h b/apps/irssi/src/core/write-buffer.h new file mode 100644 index 00000000..ef527440 --- /dev/null +++ b/apps/irssi/src/core/write-buffer.h @@ -0,0 +1,10 @@ +#ifndef __WRITE_BUFFER_H +#define __WRITE_BUFFER_H + +int write_buffer(int handle, const void *data, int size); +void write_buffer_flush(void); + +void write_buffer_init(void); +void write_buffer_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/Makefile.am b/apps/irssi/src/fe-common/core/Makefile.am new file mode 100644 index 00000000..798c271c --- /dev/null +++ b/apps/irssi/src/fe-common/core/Makefile.am @@ -0,0 +1,62 @@ +noinst_LIBRARIES = libfe_common_core.a + +include $(top_srcdir)/Makefile.defines.in + +INCLUDES = \ + $(GLIB_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/src/core/ \ + -DHELPDIR=\""$(silc_helpdir)"\" \ + -DSYSCONFDIR=\""$(silc_etcdir)"\" + +libfe_common_core_a_SOURCES = \ + autorun.c \ + chat-completion.c \ + command-history.c \ + completion.c \ + fe-channels.c \ + fe-common-core.c \ + fe-core-commands.c \ + fe-exec.c \ + fe-expandos.c \ + fe-help.c \ + fe-ignore.c \ + fe-ignore-messages.c \ + fe-log.c \ + fe-messages.c \ + fe-modules.c \ + fe-queries.c \ + fe-server.c \ + fe-settings.c \ + formats.c \ + hilight-text.c \ + keyboard.c \ + module-formats.c \ + printtext.c \ + themes.c \ + translation.c \ + window-activity.c \ + window-commands.c \ + window-items.c \ + windows-layout.c \ + fe-windows.c + +noinst_HEADERS = \ + command-history.h \ + chat-completion.h \ + completion.h \ + fe-channels.h \ + fe-common-core.h \ + fe-exec.h \ + fe-messages.h \ + fe-queries.h \ + formats.h \ + hilight-text.h \ + keyboard.h \ + module-formats.h \ + module.h \ + printtext.h \ + themes.h \ + translation.h \ + window-items.h \ + windows-layout.h \ + fe-windows.h diff --git a/apps/irssi/src/fe-common/core/autorun.c b/apps/irssi/src/fe-common/core/autorun.c new file mode 100644 index 00000000..f49b6c30 --- /dev/null +++ b/apps/irssi/src/fe-common/core/autorun.c @@ -0,0 +1,62 @@ +/* + autorun.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "line-split.h" +#include "special-vars.h" + +#include "fe-windows.h" + +static void sig_autorun(void) +{ + char tmpbuf[1024], *str, *path; + LINEBUF_REC *buffer = NULL; + int f, ret, recvlen; + + /* open ~/.silc/startup and run all commands in it */ + path = g_strdup_printf("%s/.silc/startup", g_get_home_dir()); + f = open(path, O_RDONLY); + g_free(path); + if (f == -1) { + /* file not found */ + return; + } + + do { + recvlen = read(f, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, &buffer); + if (ret > 0) eval_special_string(str, "", active_win->active_server, active_win->active); + } while (ret > 0); + line_split_free(buffer); + + close(f); +} + +void autorun_init(void) +{ + signal_add_last("irssi init finished", (SIGNAL_FUNC) sig_autorun); +} + +void autorun_deinit(void) +{ + signal_remove("irssi init finished", (SIGNAL_FUNC) sig_autorun); +} diff --git a/apps/irssi/src/fe-common/core/chat-completion.c b/apps/irssi/src/fe-common/core/chat-completion.c new file mode 100644 index 00000000..3cbcbf02 --- /dev/null +++ b/apps/irssi/src/fe-common/core/chat-completion.c @@ -0,0 +1,862 @@ +/* + chat-completion.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "settings.h" + +#include "chatnets.h" +#include "servers-setup.h" +#include "channels.h" +#include "channels-setup.h" +#include "queries.h" +#include "nicklist.h" + +#include "completion.h" +#include "window-items.h" + +static int keep_privates_count, keep_publics_count; +static int completion_lowercase; +static const char *completion_char, *cmdchars; +static GSList *global_lastmsgs; +static int completion_auto, completion_strict; + +#define SERVER_LAST_MSG_ADD(server, nick) \ + last_msg_add(&((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs, \ + nick, TRUE, keep_privates_count) + +#define CHANNEL_LAST_MSG_ADD(channel, nick, own) \ + last_msg_add(&((MODULE_CHANNEL_REC *) MODULE_DATA(channel))->lastmsgs, \ + nick, own, keep_publics_count) + +static LAST_MSG_REC *last_msg_find(GSList *list, const char *nick) +{ + while (list != NULL) { + LAST_MSG_REC *rec = list->data; + + if (g_strcasecmp(rec->nick, nick) == 0) + return rec; + list = list->next; + } + + return NULL; +} + +static void last_msg_dec_owns(GSList *list) +{ + LAST_MSG_REC *rec; + + while (list != NULL) { + rec = list->data; + if (rec->own) rec->own--; + + list = list->next; + } +} + +static void last_msg_add(GSList **list, const char *nick, int own, int max) +{ + LAST_MSG_REC *rec; + + rec = last_msg_find(*list, nick); + if (rec != NULL) { + /* msg already exists, update it */ + *list = g_slist_remove(*list, rec); + if (own) + rec->own = max; + else if (rec->own) + rec->own--; + } else { + rec = g_new(LAST_MSG_REC, 1); + rec->nick = g_strdup(nick); + + if ((int)g_slist_length(*list) == max) { + *list = g_slist_remove(*list, + g_slist_last(*list)->data); + } + + rec->own = own ? max : 0; + } + rec->time = time(NULL); + + last_msg_dec_owns(*list); + + *list = g_slist_prepend(*list, rec); +} + +static void last_msg_destroy(GSList **list, LAST_MSG_REC *rec) +{ + *list = g_slist_remove(*list, rec); + + g_free(rec->nick); + g_free(rec); +} + +void completion_last_message_add(const char *nick) +{ + g_return_if_fail(nick != NULL); + + last_msg_add(&global_lastmsgs, nick, TRUE, keep_privates_count); +} + +void completion_last_message_remove(const char *nick) +{ + LAST_MSG_REC *rec; + + g_return_if_fail(nick != NULL); + + rec = last_msg_find(global_lastmsgs, nick); + if (rec != NULL) last_msg_destroy(&global_lastmsgs, rec); +} + +void completion_last_message_rename(const char *oldnick, const char *newnick) +{ + LAST_MSG_REC *rec; + + g_return_if_fail(oldnick != NULL); + g_return_if_fail(newnick != NULL); + + rec = last_msg_find(global_lastmsgs, oldnick); + if (rec != NULL) { + g_free(rec->nick); + rec->nick = g_strdup(newnick); + } +} + +static void sig_message_public(SERVER_REC *server, const char *msg, + const char *nick, const char *address, + const char *target) +{ + CHANNEL_REC *channel; + int own; + + channel = channel_find(server, target); + if (channel != NULL) { + own = nick_match_msg(channel, msg, server->nick); + CHANNEL_LAST_MSG_ADD(channel, nick, own); + } +} + +static void sig_message_private(SERVER_REC *server, const char *msg, + const char *nick, const char *address) +{ + g_return_if_fail(server != NULL); + g_return_if_fail(nick != NULL); + + SERVER_LAST_MSG_ADD(server, nick); +} + +static void sig_message_own_public(SERVER_REC *server, const char *msg, + const char *target, const char *origtarget) +{ + CHANNEL_REC *channel; + NICK_REC *nick; + char *p, *msgnick; + + g_return_if_fail(server != NULL); + g_return_if_fail(msg != NULL); + if (target == NULL) return; + + channel = channel_find(server, target); + if (channel == NULL) + return; + + /* channel msg - if first word in line is nick, + add it to lastmsgs */ + p = strchr(msg, ' '); + if (p != NULL && p != msg) { + msgnick = g_strndup(msg, (int) (p-msg)); + nick = nicklist_find(channel, msgnick); + if (nick == NULL && msgnick[1] != '\0') { + /* probably ':' or ',' or some other + char after nick, try without it */ + msgnick[strlen(msgnick)-1] = '\0'; + nick = nicklist_find(channel, msgnick); + } + g_free(msgnick); + if (nick != NULL && nick != channel->ownnick) + CHANNEL_LAST_MSG_ADD(channel, nick->nick, TRUE); + } +} + +static void sig_message_own_private(SERVER_REC *server, const char *msg, + const char *target, const char *origtarget) +{ + g_return_if_fail(server != NULL); + g_return_if_fail(target != NULL); + + if (target != NULL && query_find(server, target) == NULL) + SERVER_LAST_MSG_ADD(server, target); +} + +static void sig_nick_removed(CHANNEL_REC *channel, NICK_REC *nick) +{ + MODULE_CHANNEL_REC *mchannel; + LAST_MSG_REC *rec; + + mchannel = MODULE_DATA(channel); + rec = last_msg_find(mchannel->lastmsgs, nick->nick); + if (rec != NULL) last_msg_destroy(&mchannel->lastmsgs, rec); +} + +static void sig_nick_changed(CHANNEL_REC *channel, NICK_REC *nick, + const char *oldnick) +{ + MODULE_CHANNEL_REC *mchannel; + LAST_MSG_REC *rec; + + mchannel = MODULE_DATA(channel); + rec = last_msg_find(mchannel->lastmsgs, oldnick); + if (rec != NULL) { + g_free(rec->nick); + rec->nick = g_strdup(nick->nick); + } +} + +static int last_msg_cmp(LAST_MSG_REC *m1, LAST_MSG_REC *m2) +{ + return m1->time < m2->time ? 1 : -1; +} + +/* Complete /MSG from specified server, or from + global_lastmsgs if server is NULL */ +static void completion_msg_server(GSList **list, SERVER_REC *server, + const char *nick, const char *prefix) +{ + LAST_MSG_REC *msg; + GSList *tmp; + int len; + + g_return_if_fail(nick != NULL); + + len = strlen(nick); + tmp = server == NULL ? global_lastmsgs : + ((MODULE_SERVER_REC *) MODULE_DATA(server))->lastmsgs; + for (; tmp != NULL; tmp = tmp->next) { + LAST_MSG_REC *rec = tmp->data; + + if (len != 0 && g_strncasecmp(rec->nick, nick, len) != 0) + continue; + + msg = g_new(LAST_MSG_REC, 1); + msg->time = rec->time; + msg->nick = prefix == NULL || *prefix == '\0' ? + g_strdup(rec->nick) : + g_strconcat(prefix, " ", rec->nick, NULL); + *list = g_slist_insert_sorted(*list, msg, + (GCompareFunc) last_msg_cmp); + } +} + +/* convert list of LAST_MSG_REC's to list of char* nicks. */ +static GList *convert_msglist(GSList *msglist) +{ + GList *list; + + list = NULL; + while (msglist != NULL) { + LAST_MSG_REC *rec = msglist->data; + + list = g_list_append(list, rec->nick); + msglist = g_slist_remove(msglist, rec); + g_free(rec); + } + + return list; +} + +/* Complete /MSG - if `find_server' is NULL, complete nicks from all servers */ +static GList *completion_msg(SERVER_REC *win_server, + SERVER_REC *find_server, + const char *nick, const char *prefix) +{ + GSList *tmp, *list; + char *newprefix; + + g_return_val_if_fail(nick != NULL, NULL); + if (servers == NULL) return NULL; + + list = NULL; + if (find_server != NULL) { + completion_msg_server(&list, find_server, nick, prefix); + return convert_msglist(list); + } + + completion_msg_server(&list, NULL, nick, prefix); + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *rec = tmp->data; + + if (rec == win_server) + newprefix = g_strdup(prefix); + else { + newprefix = prefix == NULL ? + g_strdup_printf("-%s", rec->tag) : + g_strdup_printf("%s -%s", prefix, rec->tag); + } + + completion_msg_server(&list, rec, nick, newprefix); + g_free_not_null(newprefix); + } + + return convert_msglist(list); +} + +static void complete_from_nicklist(GList **outlist, CHANNEL_REC *channel, + const char *nick, const char *suffix) +{ + MODULE_CHANNEL_REC *mchannel; + GSList *tmp; + GList *ownlist; + char *str; + int len; + + /* go through the last x nicks who have said something in the channel. + nicks of all the "own messages" are placed before others */ + ownlist = NULL; + len = strlen(nick); + mchannel = MODULE_DATA(channel); + for (tmp = mchannel->lastmsgs; tmp != NULL; tmp = tmp->next) { + LAST_MSG_REC *rec = tmp->data; + + if (g_strncasecmp(rec->nick, nick, len) == 0 && + glist_find_icase_string(*outlist, rec->nick) == NULL) { + str = g_strconcat(rec->nick, suffix, NULL); + if (completion_lowercase) g_strdown(str); + if (rec->own) + ownlist = g_list_append(ownlist, str); + else + *outlist = g_list_append(*outlist, str); + } + } + + *outlist = g_list_concat(ownlist, *outlist); +} + +static GList *completion_nicks_nonstrict(CHANNEL_REC *channel, + const char *nick, + const char *suffix) +{ + GSList *nicks, *tmp; + GList *list; + char *tnick, *str, *in, *out; + int len, str_len, tmplen; + + g_return_val_if_fail(channel != NULL, NULL); + + list = NULL; + + /* get all nicks from current channel, strip non alnum chars, + compare again and add to completion list on matching */ + len = strlen(nick); + nicks = nicklist_getnicks(channel); + + str_len = 80; str = g_malloc(str_len+1); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + tmplen = strlen(rec->nick); + if (tmplen > str_len) { + str_len = tmplen*2; + str = g_realloc(str, str_len+1); + } + + /* remove non alnum chars from nick */ + in = rec->nick; out = str; + while (*in != '\0') { + if (isalnum(*in)) + *out++ = *in; + in++; + } + *out = '\0'; + + /* add to list if 'cleaned' nick matches */ + if (g_strncasecmp(str, nick, len) == 0) { + tnick = g_strconcat(rec->nick, suffix, NULL); + if (completion_lowercase) + g_strdown(tnick); + + if (glist_find_icase_string(list, tnick) == NULL) + list = g_list_append(list, tnick); + else + g_free(tnick); + } + + } + g_free(str); + g_slist_free(nicks); + + return list; +} + +static GList *completion_channel_nicks(CHANNEL_REC *channel, const char *nick, + const char *suffix) +{ + GSList *nicks, *tmp; + GList *list; + char *str; + int len; + + g_return_val_if_fail(channel != NULL, NULL); + g_return_val_if_fail(nick != NULL, NULL); + if (*nick == '\0') return NULL; + + if (suffix != NULL && *suffix == '\0') + suffix = NULL; + + /* put first the nicks who have recently said something */ + list = NULL; + complete_from_nicklist(&list, channel, nick, suffix); + + /* and add the rest of the nicks too */ + len = strlen(nick); + nicks = nicklist_getnicks(channel); + for (tmp = nicks; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + if (g_strncasecmp(rec->nick, nick, len) == 0 && + rec != channel->ownnick) { + str = g_strconcat(rec->nick, suffix, NULL); + if (completion_lowercase) + g_strdown(str); + if (glist_find_icase_string(list, str) == NULL) + list = g_list_append(list, str); + else + g_free(str); + } + } + g_slist_free(nicks); + + /* remove non alphanum chars from nick and search again in case + list is still NULL ("foo" would match "_foo_" f.e.) */ + if (!completion_strict) + list = g_list_concat(list, completion_nicks_nonstrict(channel, nick, suffix)); + return list; +} + +/* append all strings in list2 to list1 that already aren't there and + free list2 */ +static GList *completion_joinlist(GList *list1, GList *list2) +{ + GList *old; + + old = list2; + while (list2 != NULL) { + if (!glist_find_icase_string(list1, list2->data)) + list1 = g_list_append(list1, list2->data); + else + g_free(list2->data); + + list2 = list2->next; + } + + g_list_free(old); + return list1; +} + +GList *completion_get_channels(SERVER_REC *server, const char *word) +{ + GList *list; + GSList *tmp; + int len; + + g_return_val_if_fail(word != NULL, NULL); + g_return_val_if_fail(*word != '\0', NULL); + + len = strlen(word); + list = NULL; + + /* first get the joined channels */ + tmp = server == NULL ? NULL : server->channels; + for (; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (g_strncasecmp(rec->name, word, len) == 0) + list = g_list_append(list, g_strdup(rec->name)); + } + + /* get channels from setup */ + for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) { + CHANNEL_SETUP_REC *rec = tmp->data; + + if (g_strncasecmp(rec->name, word, len) == 0 && + glist_find_icase_string(list, rec->name) == NULL) + list = g_list_append(list, g_strdup(rec->name)); + + } + + return list; +} + +static void complete_window_nicks(GList **list, WINDOW_REC *window, + const char *word, const char *linestart) +{ + CHANNEL_REC *channel; + GList *tmplist; + GSList *tmp; + const char *nicksuffix; + + nicksuffix = *linestart != '\0' ? NULL : completion_char; + + channel = CHANNEL(window->active); + + /* first the active channel */ + if (channel != NULL) { + tmplist = completion_channel_nicks(channel, word, nicksuffix); + *list = completion_joinlist(*list, tmplist); + } + + if (nicksuffix != NULL) { + /* completing nick at the start of line - probably answering + to some other nick, don't even try to complete from + non-active channels */ + return; + } + + /* then the rest */ + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + channel = CHANNEL(tmp->data); + if (channel != NULL && tmp->data != window->active) { + tmplist = completion_channel_nicks(channel, word, + nicksuffix); + *list = completion_joinlist(*list, tmplist); + } + } +} + +static void sig_complete_word(GList **list, WINDOW_REC *window, + const char *word, const char *linestart) +{ + SERVER_REC *server; + CHANNEL_REC *channel; + QUERY_REC *query; + char *prefix; + + g_return_if_fail(list != NULL); + g_return_if_fail(window != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(linestart != NULL); + + server = window->active_server; + if (server == NULL && servers != NULL) + server = servers->data; + + if (server != NULL && server->ischannel(word)) { + /* probably completing a channel name */ + *list = completion_get_channels(window->active_server, word); + return; + } + + server = window->active_server; + if (server == NULL || !server->connected) + return; + + if (*linestart == '\0' && *word == '\0') { + /* pressed TAB at the start of line - add /MSG */ + prefix = g_strdup_printf("%cmsg", *cmdchars); + *list = completion_msg(server, NULL, "", prefix); + if (*list == NULL) + *list = g_list_append(*list, g_strdup(prefix)); + g_free(prefix); + + signal_stop(); + return; + } + + channel = CHANNEL(window->active); + query = QUERY(window->active); + if (channel == NULL && query != NULL && + g_strncasecmp(word, query->name, strlen(word)) == 0) { + /* completion in query */ + *list = g_list_append(*list, g_strdup(query->name)); + } else if (channel != NULL) { + /* nick completion .. we could also be completing a nick + after /MSG from nicks in channel */ + complete_window_nicks(list, window, word, linestart); + } + + if (*list != NULL) signal_stop(); +} + +static SERVER_REC *line_get_server(const char *line) +{ + SERVER_REC *server; + char *tag, *ptr; + + g_return_val_if_fail(line != NULL, NULL); + if (*line != '-') return NULL; + + /* -option found - should be server tag */ + tag = g_strdup(line+1); + ptr = strchr(tag, ' '); + if (ptr != NULL) *ptr = '\0'; + + server = server_find_tag(tag); + + g_free(tag); + return server; +} + +static void sig_complete_msg(GList **list, WINDOW_REC *window, + const char *word, const char *line, + int *want_space) +{ + SERVER_REC *server, *msgserver; + + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(line != NULL); + + server = window->active_server; + if (server == NULL || !server->connected) + return; + + msgserver = line_get_server(line); + *list = completion_msg(server, msgserver, word, NULL); + if (*list != NULL) signal_stop(); +} + +GList *completion_get_chatnets(const char *word) +{ + GList *list; + GSList *tmp; + int len; + + g_return_val_if_fail(word != NULL, NULL); + + len = strlen(word); + list = NULL; + + for (tmp = chatnets; tmp != NULL; tmp = tmp->next) { + CHATNET_REC *rec = tmp->data; + + if (g_strncasecmp(rec->name, word, len) == 0) + list = g_list_append(list, g_strdup(rec->name)); + } + + return list; +} + +GList *completion_get_servers(const char *word) +{ + GList *list; + GSList *tmp; + int len; + + g_return_val_if_fail(word != NULL, NULL); + + len = strlen(word); + list = NULL; + + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SERVER_SETUP_REC *rec = tmp->data; + + if (g_strncasecmp(rec->address, word, len) == 0) + list = g_list_append(list, g_strdup(rec->address)); + } + + return list; +} + +static void sig_complete_connect(GList **list, WINDOW_REC *window, + const char *word, const char *line, + int *want_space) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + + *list = completion_get_chatnets(word); + *list = g_list_concat(*list, completion_get_servers(word)); + if (*list != NULL) signal_stop(); +} + +/* expand \n, \t and \\ */ +static char *expand_escapes(const char *line, SERVER_REC *server, + WI_ITEM_REC *item) +{ + char *ptr, *ret; + + ret = ptr = g_malloc(strlen(line)+1); + for (; *line != '\0'; line++) { + if (*line != '\\') { + *ptr++ = *line; + continue; + } + + line++; + if (*line == '\0') { + *ptr++ = '\\'; + break; + } + + switch (*line) { + case 'n': + /* newline .. we need to send another "send text" + event to handle it (or actually the text before + the newline..) */ + *ptr = '\0'; + signal_emit("send text", 3, ret, server, item); + ptr = ret; + break; + case 't': + *ptr++ = '\t'; + break; + case '\\': + *ptr++ = '\\'; + break; + default: + *ptr++ = '\\'; + *ptr++ = *line; + break; + } + } + + *ptr = '\0'; + return ret; +} + +static void event_text(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + CHANNEL_REC *channel; + GList *comp; + char *line, *str, *ptr, comp_char; + + g_return_if_fail(data != NULL); + if (item == NULL) return; + + line = settings_get_bool("expand_escapes") ? + expand_escapes(data, server, item) : g_strdup(data); + comp_char = *completion_char; + + /* check for automatic nick completion */ + ptr = NULL; + comp = NULL; + channel = CHANNEL(item); + + if (completion_auto && channel != NULL && comp_char != '\0') { + ptr = strchr(line, comp_char); + if (ptr != NULL) { + *ptr++ = '\0'; + if (nicklist_find(channel, line) == NULL) { + comp = completion_channel_nicks(channel, + line, NULL); + } + } + } + + str = g_strdup_printf(ptr == NULL ? "%s %s" : "%s %s%c%s", item->name, + comp != NULL ? (char *) comp->data : line, + comp_char, ptr); + signal_emit("command msg", 3, str, server, item); + + g_free(str); + g_free(line); + + if (comp != NULL) { + g_list_foreach(comp, (GFunc) g_free, NULL); + g_list_free(comp); + } + + signal_stop(); +} + +static void sig_server_disconnected(SERVER_REC *server) +{ + MODULE_SERVER_REC *mserver; + + g_return_if_fail(server != NULL); + + mserver = MODULE_DATA(server); + while (mserver->lastmsgs) + last_msg_destroy(&mserver->lastmsgs, mserver->lastmsgs->data); +} + +static void sig_channel_destroyed(CHANNEL_REC *channel) +{ + MODULE_CHANNEL_REC *mchannel; + + g_return_if_fail(channel != NULL); + + mchannel = MODULE_DATA(channel); + while (mchannel->lastmsgs != NULL) { + last_msg_destroy(&mchannel->lastmsgs, + mchannel->lastmsgs->data); + } +} + +static void read_settings(void) +{ + keep_privates_count = settings_get_int("completion_keep_privates"); + keep_publics_count = settings_get_int("completion_keep_publics"); + completion_lowercase = settings_get_bool("completion_nicks_lowercase"); + completion_char = settings_get_str("completion_char"); + cmdchars = settings_get_str("cmdchars"); + completion_auto = settings_get_bool("completion_auto"); + completion_strict = settings_get_bool("completion_strict"); +} + +void chat_completion_init(void) +{ + settings_add_str("completion", "completion_char", ":"); + settings_add_bool("completion", "completion_auto", FALSE); + settings_add_int("completion", "completion_keep_publics", 50); + settings_add_int("completion", "completion_keep_privates", 10); + settings_add_bool("completion", "expand_escapes", FALSE); + settings_add_bool("completion", "completion_nicks_lowercase", FALSE); + settings_add_bool("completion", "completion_strict", FALSE); + + read_settings(); + signal_add("complete word", (SIGNAL_FUNC) sig_complete_word); + signal_add("complete command msg", (SIGNAL_FUNC) sig_complete_msg); + signal_add("complete command connect", (SIGNAL_FUNC) sig_complete_connect); + signal_add("complete command server", (SIGNAL_FUNC) sig_complete_connect); + signal_add("message public", (SIGNAL_FUNC) sig_message_public); + signal_add("message private", (SIGNAL_FUNC) sig_message_private); + signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public); + signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_add("nicklist remove", (SIGNAL_FUNC) sig_nick_removed); + signal_add("nicklist changed", (SIGNAL_FUNC) sig_nick_changed); + signal_add("send text", (SIGNAL_FUNC) event_text); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void chat_completion_deinit(void) +{ + while (global_lastmsgs != NULL) + last_msg_destroy(&global_lastmsgs, global_lastmsgs->data); + + signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word); + signal_remove("complete command msg", (SIGNAL_FUNC) sig_complete_msg); + signal_remove("complete command connect", (SIGNAL_FUNC) sig_complete_connect); + signal_remove("complete command server", (SIGNAL_FUNC) sig_complete_connect); + signal_remove("message public", (SIGNAL_FUNC) sig_message_public); + signal_remove("message private", (SIGNAL_FUNC) sig_message_private); + signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public); + signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nick_removed); + signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nick_changed); + signal_remove("send text", (SIGNAL_FUNC) event_text); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/fe-common/core/chat-completion.h b/apps/irssi/src/fe-common/core/chat-completion.h new file mode 100644 index 00000000..3cb70ca5 --- /dev/null +++ b/apps/irssi/src/fe-common/core/chat-completion.h @@ -0,0 +1,8 @@ +#ifndef __CHAT_COMPLETION_H +#define __CHAT_COMPLETION_H + +void completion_last_message_add(const char *nick); +void completion_last_message_remove(const char *nick); +void completion_last_message_rename(const char *oldnick, const char *newnick); + +#endif diff --git a/apps/irssi/src/fe-common/core/command-history.c b/apps/irssi/src/fe-common/core/command-history.c new file mode 100644 index 00000000..cdf4b5a5 --- /dev/null +++ b/apps/irssi/src/fe-common/core/command-history.c @@ -0,0 +1,192 @@ +/* + command-history.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "special-vars.h" +#include "settings.h" + +#include "fe-windows.h" +#include "window-items.h" + +/* command history */ +static GList *history, *history_pos; +static int history_lines, history_over_counter; +static int window_history; + +void command_history_add(WINDOW_REC *window, const char *text) +{ + GList **phistory, *link; + int *phistory_lines; + + g_return_if_fail(text != NULL); + + if (window_history) { + /* window specific command history */ + phistory = &window->history; + phistory_lines = &window->history_lines; + } else { + /* global command history */ + phistory = &history; + phistory_lines = &history_lines; + } + + if (settings_get_int("max_command_history") < 1 || *phistory_lines < settings_get_int("max_command_history")) + (*phistory_lines)++; + else { + link = *phistory; + g_free(link->data); + *phistory = g_list_remove_link(*phistory, link); + g_list_free_1(link); + } + + *phistory = g_list_append(*phistory, g_strdup(text)); +} + +const char *command_history_prev(WINDOW_REC *window, const char *text) +{ + GList *pos, **phistory_pos; + int *phistory_over_counter; + + if (window_history) { + phistory_pos = &window->history_pos; + phistory_over_counter = &window->history_over_counter; + } else { + phistory_pos = &history_pos; + phistory_over_counter = &history_over_counter; + } + + pos = *phistory_pos; + if (*phistory_pos != NULL) { + *phistory_pos = (*phistory_pos)->prev; + if (*phistory_pos == NULL) + (*phistory_over_counter)++; + } else { + *phistory_pos = g_list_last(window_history ? + window->history : history); + } + + if (*text != '\0' && + (pos == NULL || strcmp(pos->data, text) != 0)) { + /* save the old entry to history */ + command_history_add(window, text); + } + + return *phistory_pos == NULL ? "" : (*phistory_pos)->data; +} + +const char *command_history_next(WINDOW_REC *window, const char *text) +{ + GList *pos, **phistory_pos; + int *phistory_over_counter; + + if (window_history) { + phistory_pos = &window->history_pos; + phistory_over_counter = &window->history_over_counter; + } else { + phistory_pos = &history_pos; + phistory_over_counter = &history_over_counter; + } + + pos = *phistory_pos; + + if (pos != NULL) + *phistory_pos = (*phistory_pos)->next; + else if (*phistory_over_counter > 0) { + (*phistory_over_counter)--; + *phistory_pos = window_history ? window->history : history; + } + + if (*text != '\0' && + (pos == NULL || strcmp(pos->data, text) != 0)) { + /* save the old entry to history */ + command_history_add(window, text); + } + return *phistory_pos == NULL ? "" : (*phistory_pos)->data; +} + +void command_history_clear_pos(WINDOW_REC *window) +{ + window->history_over_counter = 0; + window->history_pos = NULL; + history_over_counter = 0; + history_pos = NULL; +} + +static void sig_window_destroyed(WINDOW_REC *window) +{ + g_list_foreach(window->history, (GFunc) g_free, NULL); + g_list_free(window->history); +} + +static char *special_history_func(const char *text, void *item, int *free_ret) +{ + WINDOW_REC *window; + GList *tmp; + char *findtext, *ret; + + window = item == NULL ? active_win : window_item_window(item); + + findtext = g_strdup_printf("*%s*", text); + ret = NULL; + + tmp = window_history ? window->history : history; + for (; tmp != NULL; tmp = tmp->next) { + const char *line = tmp->data; + + if (match_wildcards(findtext, line)) { + *free_ret = TRUE; + ret = g_strdup(line); + } + } + g_free(findtext); + + return ret; +} + +static void read_settings(void) +{ + window_history = settings_get_bool("window_history"); +} + +void command_history_init(void) +{ + settings_add_int("history", "max_command_history", 100); + settings_add_bool("history", "window_history", FALSE); + + special_history_func_set(special_history_func); + + history_lines = 0; history_over_counter = 0; + history = NULL; history_pos = NULL; + + read_settings(); + signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void command_history_deinit(void) +{ + signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + + g_list_foreach(history, (GFunc) g_free, NULL); + g_list_free(history); +} diff --git a/apps/irssi/src/fe-common/core/command-history.h b/apps/irssi/src/fe-common/core/command-history.h new file mode 100644 index 00000000..9f37f069 --- /dev/null +++ b/apps/irssi/src/fe-common/core/command-history.h @@ -0,0 +1,16 @@ +#ifndef __COMMAND_HISTORY_H +#define __COMMAND_HISTORY_H + +#include "fe-windows.h" + +void command_history_init(void); +void command_history_deinit(void); + +void command_history_add(WINDOW_REC *window, const char *text, int prepend); + +const char *command_history_prev(WINDOW_REC *window, const char *text); +const char *command_history_next(WINDOW_REC *window, const char *text); + +void command_history_clear_pos(WINDOW_REC *window); + +#endif diff --git a/apps/irssi/src/fe-common/core/completion.c b/apps/irssi/src/fe-common/core/completion.c new file mode 100644 index 00000000..16427610 --- /dev/null +++ b/apps/irssi/src/fe-common/core/completion.c @@ -0,0 +1,681 @@ +/* + completion.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "completion.h" + +#define wordreplace_find(word) \ + iconfig_list_find("replaces", "text", word, "replace") + +#define completion_find(completion) \ + iconfig_list_find("completions", "short", completion, "long") + +static GList *complist; /* list of commands we're currently completing */ +static char *last_line; +static int last_want_space, last_line_pos; + +#define isseparator_notspace(c) \ + ((c) == ',') + +#define isseparator(c) \ + (isspace((int) (c)) || isseparator_notspace(c)) + +void chat_completion_init(void); +void chat_completion_deinit(void); + +/* Return whole word at specified position in string */ +char *get_word_at(const char *str, int pos, char **startpos) +{ + const char *start, *end; + + g_return_val_if_fail(str != NULL, NULL); + g_return_val_if_fail(pos >= 0, NULL); + + /* get previous word if char at `pos' is space */ + start = str+pos; + while (start > str && isseparator(start[-1])) start--; + + end = start; + while (start > str && !isseparator(start[-1])) start--; + while (*end != '\0' && !isseparator(*end)) end++; + while (*end != '\0' && isseparator_notspace(*end)) end++; + + *startpos = (char *) start; + return g_strndup(start, (int) (end-start)); +} + +/* automatic word completion - called when space/enter is pressed */ +char *auto_word_complete(const char *line, int *pos) +{ + GString *result; + const char *replace; + char *word, *wordstart, *ret; + int startpos; + + g_return_val_if_fail(line != NULL, NULL); + g_return_val_if_fail(pos != NULL, NULL); + + word = get_word_at(line, *pos, &wordstart); + startpos = (int) (wordstart-line); + + result = g_string_new(line); + g_string_erase(result, startpos, strlen(word)); + + /* check for words in autocompletion list */ + replace = wordreplace_find(word); + if (replace == NULL) { + ret = NULL; + g_string_free(result, TRUE); + } else { + *pos = startpos+strlen(replace); + + g_string_insert(result, startpos, replace); + ret = result->str; + g_string_free(result, FALSE); + } + + g_free(word); + return ret; +} + +static void free_completions(void) +{ + complist = g_list_first(complist); + + g_list_foreach(complist, (GFunc) g_free, NULL); + g_list_free(complist); + complist = NULL; + + g_free_and_null(last_line); +} + +/* manual word completion - called when TAB is pressed */ +char *word_complete(WINDOW_REC *window, const char *line, int *pos) +{ + static int startpos = 0, wordlen = 0; + + GString *result; + char *word, *wordstart, *linestart, *ret; + int want_space; + + g_return_val_if_fail(line != NULL, NULL); + g_return_val_if_fail(pos != NULL, NULL); + + if (complist != NULL && *pos == last_line_pos && + strcmp(line, last_line) == 0) { + /* complete from old list */ + complist = complist->next != NULL ? complist->next : + g_list_first(complist); + want_space = last_want_space; + } else { + /* get new completion list */ + free_completions(); + + /* get the word we want to complete */ + word = get_word_at(line, *pos, &wordstart); + startpos = (int) (wordstart-line); + wordlen = strlen(word); + + /* get the start of line until the word we're completing */ + if (isseparator(*line)) { + /* empty space at the start of line */ + if (wordstart == line) + wordstart += strlen(wordstart); + } else { + while (wordstart > line && isseparator(wordstart[-1])) + wordstart--; + } + linestart = g_strndup(line, (int) (wordstart-line)); + + /* completions usually add space after the word, that makes + things a bit harder. When continuing a completion + "/msg nick1 " we have to cycle to nick2, etc. + BUT if we start completion with "/msg ", we don't + want to complete the /msg word, but instead complete empty + word with /msg being in linestart. */ + if (*pos > 0 && line[*pos-1] == ' ') { + char *old; + + old = linestart; + linestart = *linestart == '\0' ? + g_strdup(word) : + g_strconcat(linestart, " ", word, NULL); + g_free(old); + + g_free(word); + word = g_strdup(""); + startpos = strlen(linestart)+1; + wordlen = 0; + } + + want_space = TRUE; + signal_emit("complete word", 5, &complist, window, word, linestart, &want_space); + last_want_space = want_space; + + g_free(linestart); + g_free(word); + } + + if (complist == NULL) + return NULL; + + /* word completed */ + *pos = startpos+strlen(complist->data); + + /* replace the word in line - we need to return + a full new line */ + result = g_string_new(line); + g_string_erase(result, startpos, wordlen); + g_string_insert(result, startpos, complist->data); + + if (want_space) { + if (!isseparator(result->str[*pos])) + g_string_insert_c(result, *pos, ' '); + (*pos)++; + } + + wordlen = strlen(complist->data); + last_line_pos = *pos; + g_free_not_null(last_line); + last_line = g_strdup(result->str); + + ret = result->str; + g_string_free(result, FALSE); + return ret; +} + +GList *list_add_file(GList *list, const char *name) +{ + struct stat statbuf; + char *fname; + + g_return_val_if_fail(name != NULL, NULL); + + fname = convert_home(name); + if (stat(fname, &statbuf) == 0) { + list = g_list_append(list, !S_ISDIR(statbuf.st_mode) ? g_strdup(name) : + g_strconcat(name, G_DIR_SEPARATOR_S, NULL)); + } + + g_free(fname); + return list; +} + +GList *filename_complete(const char *path) +{ + GList *list; + DIR *dirp; + struct dirent *dp; + char *realpath, *dir, *basename, *name; + int len; + + g_return_val_if_fail(path != NULL, NULL); + + list = NULL; + + /* get directory part of the path - expand ~/ */ + realpath = convert_home(path); + dir = g_dirname(realpath); + g_free(realpath); + + /* open directory for reading */ + dirp = opendir(dir); + g_free(dir); + if (dirp == NULL) return NULL; + + dir = g_dirname(path); + if (*dir == G_DIR_SEPARATOR && dir[1] == '\0') + *dir = '\0'; /* completing file in root directory */ + basename = g_basename(path); + len = strlen(basename); + + /* add all files in directory to completion list */ + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.') { + if (dp->d_name[1] == '\0' || + (dp->d_name[1] == '.' && dp->d_name[2] == '\0')) + continue; /* skip . and .. */ + + if (basename[0] != '.') + continue; + } + + if (len == 0 || strncmp(dp->d_name, basename, len) == 0) { + name = g_strdup_printf("%s"G_DIR_SEPARATOR_S"%s", dir, dp->d_name); + list = list_add_file(list, name); + g_free(name); + } + } + closedir(dirp); + + g_free(dir); + return list; +} + +static GList *completion_get_settings(const char *key) +{ + GList *complist; + GSList *tmp, *sets; + int len; + + g_return_val_if_fail(key != NULL, NULL); + + sets = settings_get_sorted(); + + len = strlen(key); + complist = NULL; + for (tmp = sets; tmp != NULL; tmp = tmp->next) { + SETTINGS_REC *rec = tmp->data; + + if (g_strncasecmp(rec->key, key, len) == 0) + complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp); + } + g_slist_free(sets); + return complist; +} + +static GList *completion_get_bool_settings(const char *key) +{ + GList *complist; + GSList *tmp, *sets; + int len; + + g_return_val_if_fail(key != NULL, NULL); + + sets = settings_get_sorted(); + + len = strlen(key); + complist = NULL; + for (tmp = sets; tmp != NULL; tmp = tmp->next) { + SETTINGS_REC *rec = tmp->data; + + if (rec->type == SETTING_TYPE_BOOLEAN && + g_strncasecmp(rec->key, key, len) == 0) + complist = g_list_insert_sorted(complist, g_strdup(rec->key), (GCompareFunc) g_istr_cmp); + } + g_slist_free(sets); + return complist; +} + +static GList *completion_get_aliases(const char *alias, char cmdchar) +{ + CONFIG_NODE *node; + GList *complist; + GSList *tmp; + char *word; + int len; + + g_return_val_if_fail(alias != NULL, NULL); + + /* get list of aliases from mainconfig */ + node = iconfig_node_traverse("aliases", FALSE); + tmp = node == NULL ? NULL : node->value; + + len = strlen(alias); + complist = NULL; + for (; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + if (node->type != NODE_TYPE_KEY) + continue; + + if (g_strncasecmp(node->key, alias, len) == 0) { + word = g_strdup_printf("%c%s", cmdchar, node->key); + /* add matching alias to completion list, aliases will + be appended after command completions and kept in + uppercase to show it's an alias */ + if (glist_find_icase_string(complist, word) == NULL) + complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp); + else + g_free(word); + } + } + return complist; +} + +static GList *completion_get_commands(const char *cmd, char cmdchar) +{ + GList *complist; + GSList *tmp; + char *word; + int len; + + g_return_val_if_fail(cmd != NULL, NULL); + + len = strlen(cmd); + complist = NULL; + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (strchr(rec->cmd, ' ') != NULL) + continue; + + if (g_strncasecmp(rec->cmd, cmd, len) == 0) { + word = cmdchar == '\0' ? g_strdup(rec->cmd) : + g_strdup_printf("%c%s", cmdchar, rec->cmd); + if (glist_find_icase_string(complist, word) == NULL) + complist = g_list_insert_sorted(complist, word, (GCompareFunc) g_istr_cmp); + else + g_free(word); + } + } + return complist; +} + +static GList *completion_get_subcommands(const char *cmd) +{ + GList *complist; + GSList *tmp; + char *spacepos; + int len, skip; + + g_return_val_if_fail(cmd != NULL, NULL); + + /* get the number of chars to skip at the start of command. */ + spacepos = strrchr(cmd, ' '); + skip = spacepos == NULL ? strlen(cmd)+1 : + ((int) (spacepos-cmd) + 1); + + len = strlen(cmd); + complist = NULL; + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if ((int)strlen(rec->cmd) < len) + continue; + + if (strchr(rec->cmd+len, ' ') != NULL) + continue; + + if (g_strncasecmp(rec->cmd, cmd, len) == 0) + complist = g_list_insert_sorted(complist, g_strdup(rec->cmd+skip), (GCompareFunc) g_istr_cmp); + } + return complist; +} + +GList *completion_get_options(const char *cmd, const char *option) +{ + COMMAND_REC *rec; + GList *list; + char **tmp; + int len; + + g_return_val_if_fail(cmd != NULL, NULL); + g_return_val_if_fail(option != NULL, NULL); + + rec = command_find(cmd); + if (rec == NULL || rec->options == NULL) return NULL; + + list = NULL; + len = strlen(option); + for (tmp = rec->options; *tmp != NULL; tmp++) { + const char *optname = *tmp + iscmdtype(**tmp); + + if (len == 0 || g_strncasecmp(optname, option, len) == 0) + list = g_list_append(list, g_strconcat("-", optname, NULL)); + } + + return list; +} + +/* split the line to command and arguments */ +static char *line_get_command(const char *line, char **args, int aliases) +{ + const char *ptr, *cmdargs; + char *cmd, *checkcmd; + + g_return_val_if_fail(line != NULL, NULL); + g_return_val_if_fail(args != NULL, NULL); + + cmd = checkcmd = NULL; *args = ""; + cmdargs = NULL; ptr = line; + + do { + ptr = strchr(ptr, ' '); + if (ptr == NULL) { + checkcmd = g_strdup(line); + cmdargs = ""; + } else { + checkcmd = g_strndup(line, (int) (ptr-line)); + + while (isspace(*ptr)) ptr++; + cmdargs = ptr; + } + + if (aliases ? !alias_find(checkcmd) : + !command_find(checkcmd)) { + /* not found, use the previous */ + g_free(checkcmd); + break; + } + + /* found, check if it has subcommands */ + g_free_not_null(cmd); + if (!aliases) + cmd = checkcmd; + else { + cmd = g_strdup(alias_find(checkcmd)); + g_free(checkcmd); + } + *args = (char *) cmdargs; + } while (ptr != NULL); + + return cmd; +} + +static char *expand_aliases(const char *line) +{ + char *cmd, *args, *ret; + + g_return_val_if_fail(line != NULL, NULL); + + cmd = line_get_command(line, &args, TRUE); + if (cmd == NULL) return g_strdup(line); + if (*args == '\0') return cmd; + + ret = g_strconcat(cmd, " ", args, NULL); + g_free(cmd); + return ret; +} + +static void sig_complete_word(GList **list, WINDOW_REC *window, + const char *word, const char *linestart, int *want_space) +{ + const char *newword, *cmdchars; + char *signal, *cmd, *args, *line; + + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(linestart != NULL); + + /* check against "completion words" list */ + newword = completion_find(word); + if (newword != NULL) { + *list = g_list_append(*list, g_strdup(newword)); + + signal_stop(); + return; + } + + /* command completion? */ + cmdchars = settings_get_str("cmdchars"); + if (strchr(cmdchars, *word) && *linestart == '\0') { + /* complete /command */ + *list = completion_get_commands(word+1, *word); + + /* complete aliases, too */ + *list = g_list_concat(*list, + completion_get_aliases(word+1, *word)); + + if (*list != NULL) signal_stop(); + return; + } + + /* check only for /command completions from now on */ + cmdchars = strchr(cmdchars, *linestart); + if (cmdchars == NULL) return; + + /* check if there's aliases */ + line = linestart[1] == *cmdchars ? g_strdup(linestart+2) : + expand_aliases(linestart+1); + + cmd = line_get_command(line, &args, FALSE); + if (cmd == NULL) { + g_free(line); + return; + } + + /* we're completing -option? */ + if (*word == '-') { + *list = completion_get_options(cmd, word+1); + g_free(cmd); + g_free(line); + return; + } + + /* complete parameters */ + signal = g_strconcat("complete command ", cmd, NULL); + signal_emit(signal, 5, list, window, word, args, want_space); + + if (command_have_sub(line)) { + /* complete subcommand */ + g_free(cmd); + cmd = g_strconcat(line, " ", word, NULL); + *list = g_list_concat(completion_get_subcommands(cmd), *list); + + if (*list != NULL) signal_stop(); + } + + g_free(signal); + g_free(cmd); + + g_free(line); +} + +static void sig_complete_set(GList **list, WINDOW_REC *window, + const char *word, const char *line, int *want_space) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(line != NULL); + + if (*line != '\0') return; + + *list = completion_get_settings(word); + if (*list != NULL) signal_stop(); +} + +static void sig_complete_toggle(GList **list, WINDOW_REC *window, + const char *word, const char *line, int *want_space) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(line != NULL); + + if (*line != '\0') return; + + *list = completion_get_bool_settings(word); + if (*list != NULL) signal_stop(); +} + +/* first argument of command is file name - complete it */ +static void sig_complete_filename(GList **list, WINDOW_REC *window, + const char *word, const char *line, int *want_space) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(line != NULL); + + if (*line != '\0') return; + + *list = filename_complete(word); + if (*list != NULL) { + *want_space = FALSE; + signal_stop(); + } +} + +/* first argument of command is .. command :) (/HELP command) */ +static void sig_complete_command(GList **list, WINDOW_REC *window, + const char *word, const char *line, int *want_space) +{ + char *cmd; + + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(line != NULL); + + if (*line == '\0') { + /* complete base command */ + *list = completion_get_commands(word, '\0'); + } else if (command_have_sub(line)) { + /* complete subcommand */ + cmd = g_strconcat(line, " ", word, NULL); + *list = completion_get_subcommands(cmd); + g_free(cmd); + } + + if (*list != NULL) signal_stop(); +} + +void completion_init(void) +{ + complist = NULL; + last_line = NULL; last_line_pos = -1; + + chat_completion_init(); + + signal_add_first("complete word", (SIGNAL_FUNC) sig_complete_word); + signal_add("complete command set", (SIGNAL_FUNC) sig_complete_set); + signal_add("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle); + signal_add("complete command cat", (SIGNAL_FUNC) sig_complete_filename); + signal_add("complete command run", (SIGNAL_FUNC) sig_complete_filename); + signal_add("complete command save", (SIGNAL_FUNC) sig_complete_filename); + signal_add("complete command reload", (SIGNAL_FUNC) sig_complete_filename); + signal_add("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename); + signal_add("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename); + signal_add("complete command help", (SIGNAL_FUNC) sig_complete_command); +} + +void completion_deinit(void) +{ + free_completions(); + + chat_completion_deinit(); + + signal_remove("complete word", (SIGNAL_FUNC) sig_complete_word); + signal_remove("complete command set", (SIGNAL_FUNC) sig_complete_set); + signal_remove("complete command toggle", (SIGNAL_FUNC) sig_complete_toggle); + signal_remove("complete command cat", (SIGNAL_FUNC) sig_complete_filename); + signal_remove("complete command run", (SIGNAL_FUNC) sig_complete_filename); + signal_remove("complete command save", (SIGNAL_FUNC) sig_complete_filename); + signal_remove("complete command reload", (SIGNAL_FUNC) sig_complete_filename); + signal_remove("complete command rawlog open", (SIGNAL_FUNC) sig_complete_filename); + signal_remove("complete command rawlog save", (SIGNAL_FUNC) sig_complete_filename); + signal_remove("complete command help", (SIGNAL_FUNC) sig_complete_command); +} + + diff --git a/apps/irssi/src/fe-common/core/completion.h b/apps/irssi/src/fe-common/core/completion.h new file mode 100644 index 00000000..ef0fe06f --- /dev/null +++ b/apps/irssi/src/fe-common/core/completion.h @@ -0,0 +1,16 @@ +#ifndef __COMPLETION_H +#define __COMPLETION_H + +#include "window-items.h" + +/* automatic word completion - called when space/enter is pressed */ +char *auto_word_complete(const char *line, int *pos); +/* manual word completion - called when TAB is pressed */ +char *word_complete(WINDOW_REC *window, const char *line, int *pos); + +GList *filename_complete(const char *path); + +void completion_init(void); +void completion_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/fe-channels.c b/apps/irssi/src/fe-common/core/fe-channels.c new file mode 100644 index 00000000..67557b91 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-channels.c @@ -0,0 +1,611 @@ +/* + fe-channels.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "chatnets.h" +#include "channels.h" +#include "channels-setup.h" +#include "nicklist.h" + +#include "fe-windows.h" +#include "fe-channels.h" +#include "window-items.h" +#include "printtext.h" + +static void signal_channel_created(CHANNEL_REC *channel, void *automatic) +{ + if (window_item_window(channel) == NULL) { + window_item_create((WI_ITEM_REC *) channel, + GPOINTER_TO_INT(automatic)); + } +} + +static void signal_channel_created_curwin(CHANNEL_REC *channel) +{ + g_return_if_fail(channel != NULL); + + window_item_add(active_win, (WI_ITEM_REC *) channel, FALSE); +} + +static void signal_channel_destroyed(CHANNEL_REC *channel) +{ + WINDOW_REC *window; + + g_return_if_fail(channel != NULL); + + window = window_item_window((WI_ITEM_REC *) channel); + if (window == NULL) + return; + + window_item_destroy((WI_ITEM_REC *) channel); + + if (channel->joined && !channel->left && + channel->server != NULL) { + /* kicked out from channel */ + window_bind_add(window, channel->server->tag, + channel->name); + } else if (!channel->joined || channel->left) + window_auto_destroy(window); +} + +static void signal_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item) +{ + CHANNEL_REC *channel; + + g_return_if_fail(window != NULL); + + channel = CHANNEL(item); + if (channel != NULL) channel_destroy(channel); +} + +static void sig_disconnected(SERVER_REC *server) +{ + WINDOW_REC *window; + GSList *tmp; + + g_return_if_fail(IS_SERVER(server)); + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + window = window_item_window((WI_ITEM_REC *) channel); + window_bind_add(window, server->tag, channel->name); + } +} + +static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(window != NULL); + if (item == NULL) return; + + if (g_slist_length(window->items) > 1 && IS_CHANNEL(item)) { + printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE, + TXT_TALKING_IN, item->name); + signal_stop(); + } +} + +static void cmd_wjoin_pre(const char *data) +{ + GHashTable *optlist; + char *nick; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, + "join", &optlist, &nick)) + return; + + if (g_hash_table_lookup(optlist, "window") != NULL) { + signal_add("channel created", + (SIGNAL_FUNC) signal_channel_created_curwin); + } + cmd_params_free(free_arg); +} + +static void cmd_join(const char *data, SERVER_REC *server) +{ + WINDOW_REC *window; + CHANNEL_REC *channel; + + if (strchr(data, ' ') != NULL || strchr(data, ',') != NULL) + return; + + channel = channel_find(server, data); + if (channel == NULL) + return; + + window = window_item_window(channel); + + if (window == active_win) { + /* channel is in active window, set it active */ + window_item_set_active(active_win, + (WI_ITEM_REC *) channel); + } else { + /* notify user how to move the channel to active + window. this was used to be done automatically + but it just confused everyone who did it + accidentally */ + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_CHANNEL_MOVE_NOTIFY, channel->name, + window->refnum); + } +} + +static void cmd_wjoin_post(const char *data) +{ + GHashTable *optlist; + char *nick; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, + "join", &optlist, &nick)) + return; + + if (g_hash_table_lookup(optlist, "window") != NULL) { + signal_remove("channel created", + (SIGNAL_FUNC) signal_channel_created_curwin); + } + cmd_params_free(free_arg); +} + +static void cmd_channel_list_joined(void) +{ + CHANNEL_REC *channel; + GString *nicks; + GSList *nicklist, *tmp, *ntmp; + + if (channels == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_IN_CHANNELS); + return; + } + + /* print active channel */ + channel = CHANNEL(active_win->active); + if (channel != NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CURRENT_CHANNEL, channel->name); + + /* print list of all channels, their modes, server tags and nicks */ + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_HEADER); + for (tmp = channels; tmp != NULL; tmp = tmp->next) { + channel = tmp->data; + + nicklist = nicklist_getnicks(channel); + nicks = g_string_new(NULL); + for (ntmp = nicklist; ntmp != NULL; ntmp = ntmp->next) { + NICK_REC *rec = ntmp->data; + + g_string_sprintfa(nicks, "%s ", rec->nick); + } + + if (nicks->len > 1) g_string_truncate(nicks, nicks->len-1); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANLIST_LINE, + channel->name, channel->mode, channel->server->tag, nicks->str); + + g_slist_free(nicklist); + g_string_free(nicks, TRUE); + } +} + +/* SYNTAX: CHANNEL LIST */ +static void cmd_channel_list(void) +{ + GString *str; + GSList *tmp; + + str = g_string_new(NULL); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_HEADER); + for (tmp = setupchannels; tmp != NULL; tmp = tmp->next) { + CHANNEL_SETUP_REC *rec = tmp->data; + + g_string_truncate(str, 0); + if (rec->autojoin) + g_string_append(str, "autojoin, "); + if (rec->botmasks != NULL && *rec->botmasks != '\0') + g_string_sprintfa(str, "bots: %s, ", rec->botmasks); + if (rec->autosendcmd != NULL && *rec->autosendcmd != '\0') + g_string_sprintfa(str, "botcmd: %s, ", rec->autosendcmd); + + if (str->len > 2) g_string_truncate(str, str->len-2); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_LINE, + rec->name, rec->chatnet == NULL ? "" : rec->chatnet, + rec->password == NULL ? "" : rec->password, str->str); + } + g_string_free(str, TRUE); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_CHANSETUP_FOOTER); +} + +static void cmd_channel(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + if (*data == '\0') + cmd_channel_list_joined(); + else + command_runsub("channel", data, server, item); +} + +/* SYNTAX: CHANNEL ADD [-auto | -noauto] [-bots ] [-botcmd ] + [] */ +static void cmd_channel_add(const char *data) +{ + GHashTable *optlist; + CHATNET_REC *chatnetrec; + CHANNEL_SETUP_REC *rec; + char *botarg, *botcmdarg, *chatnet, *channel, *password; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS, + "channel add", &optlist, &channel, &chatnet, &password)) + return; + + if (*chatnet == '\0' || *channel == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chatnetrec = chatnet_find(chatnet); + if (chatnetrec == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_UNKNOWN_CHATNET, chatnet); + cmd_params_free(free_arg); + return; + } + + botarg = g_hash_table_lookup(optlist, "bots"); + botcmdarg = g_hash_table_lookup(optlist, "botcmd"); + + rec = channel_setup_find(channel, chatnet); + if (rec == NULL) { + rec = CHAT_PROTOCOL(chatnetrec)->create_channel_setup(); + rec->name = g_strdup(channel); + rec->chatnet = g_strdup(chatnet); + } else { + if (g_hash_table_lookup(optlist, "bots")) g_free_and_null(rec->botmasks); + if (g_hash_table_lookup(optlist, "botcmd")) g_free_and_null(rec->autosendcmd); + if (*password != '\0') g_free_and_null(rec->password); + } + if (g_hash_table_lookup(optlist, "auto")) rec->autojoin = TRUE; + if (g_hash_table_lookup(optlist, "noauto")) rec->autojoin = FALSE; + if (botarg != NULL && *botarg != '\0') rec->botmasks = g_strdup(botarg); + if (botcmdarg != NULL && *botcmdarg != '\0') rec->autosendcmd = g_strdup(botcmdarg); + if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password); + + signal_emit("channel add fill", 2, rec, optlist); + + channel_setup_create(rec); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_CHANSETUP_ADDED, channel, chatnet); + + cmd_params_free(free_arg); +} + +/* SYNTAX: CHANNEL REMOVE */ +static void cmd_channel_remove(const char *data) +{ + CHANNEL_SETUP_REC *rec; + char *chatnet, *channel; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 2, &channel, &chatnet)) + return; + if (*chatnet == '\0' || *channel == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + rec = channel_setup_find(channel, chatnet); + if (rec == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_NOT_FOUND, channel, chatnet); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CHANSETUP_REMOVED, channel, chatnet); + channel_setup_remove(rec); + } + cmd_params_free(free_arg); +} + +static int get_nick_length(void *data) +{ + return strlen(((NICK_REC *) data)->nick); +} + +static void display_sorted_nicks(CHANNEL_REC *channel, GSList *nicklist) +{ + WINDOW_REC *window; + TEXT_DEST_REC dest; + GString *str; + GSList *tmp; + char *format, *stripped; + char *linebuf, nickmode[2] = { 0, 0 }; + int *columns, cols, rows, last_col_rows, col, row, max_width; + int item_extra, linebuf_size; + + window = window_find_closest(channel->server, channel->name, + MSGLEVEL_CLIENTCRAP); + max_width = window->width; + + /* get the length of item extra stuff ("[ ] ") */ + format = format_get_text(MODULE_NAME, NULL, + channel->server, channel->name, + TXT_NAMES_NICK, " ", ""); + stripped = strip_codes(format); + item_extra = strlen(stripped); + g_free(stripped); + g_free(format); + + if (settings_get_int("names_max_width") > 0 && + max_width > settings_get_int("names_max_width")) + max_width = settings_get_int("names_max_width"); + + /* remove width of timestamp from max_width */ + format_create_dest(&dest, channel->server, channel->name, + MSGLEVEL_CLIENTCRAP, NULL); + format = format_get_line_start(current_theme, &dest, time(NULL)); + if (format != NULL) { + stripped = strip_codes(format); + max_width -= strlen(stripped); + g_free(stripped); + g_free(format); + } + + /* calculate columns */ + cols = get_max_column_count(nicklist, get_nick_length, max_width, + settings_get_int("names_max_columns"), + item_extra, 3, &columns, &rows); + nicklist = columns_sort_list(nicklist, rows); + + /* rows in last column */ + last_col_rows = rows-(cols*rows-g_slist_length(nicklist)); + if (last_col_rows == 0) + last_col_rows = rows; + + str = g_string_new(NULL); + linebuf_size = max_width+1; linebuf = g_malloc(linebuf_size); + + col = 0; row = 0; + for (tmp = nicklist; tmp != NULL; tmp = tmp->next) { + NICK_REC *rec = tmp->data; + + nickmode[0] = rec->op ? '@' : rec->voice ? '+' : ' '; + + if (linebuf_size < columns[col]-item_extra+1) { + linebuf_size = (columns[col]-item_extra+1)*2; + linebuf = g_realloc(linebuf, linebuf_size); + } + memset(linebuf, ' ', columns[col]-item_extra); + linebuf[columns[col]-item_extra] = '\0'; + memcpy(linebuf, rec->nick, strlen(rec->nick)); + + format = format_get_text(MODULE_NAME, NULL, + channel->server, channel->name, + TXT_NAMES_NICK, nickmode, linebuf); + g_string_append(str, format); + g_free(format); + + if (++col == cols) { + printtext(channel->server, channel->name, + MSGLEVEL_CLIENTCRAP, "%s", str->str); + g_string_truncate(str, 0); + col = 0; row++; + + if (row == last_col_rows) + cols--; + } + } + + if (str->len != 0) { + printtext(channel->server, channel->name, + MSGLEVEL_CLIENTCRAP, "%s", str->str); + } + + g_slist_free(nicklist); + g_string_free(str, TRUE); + g_free_not_null(columns); + g_free(linebuf); +} + +void fe_channels_nicklist(CHANNEL_REC *channel, int flags) +{ + NICK_REC *nick; + GSList *tmp, *nicklist, *sorted; + int nicks, normal, voices, ops; + + nicks = normal = voices = ops = 0; + nicklist = nicklist_getnicks(channel); + sorted = NULL; + + /* sort the nicklist */ + for (tmp = nicklist; tmp != NULL; tmp = tmp->next) { + nick = tmp->data; + + nicks++; + if (nick->op) { + ops++; + if ((flags & CHANNEL_NICKLIST_FLAG_OPS) == 0) + continue; + } else if (nick->halfop) { + if ((flags & CHANNEL_NICKLIST_FLAG_HALFOPS) == 0) + continue; + } else if (nick->voice) { + voices++; + if ((flags & CHANNEL_NICKLIST_FLAG_VOICES) == 0) + continue; + } else { + normal++; + if ((flags & CHANNEL_NICKLIST_FLAG_NORMAL) == 0) + continue; + } + + sorted = g_slist_insert_sorted(sorted, nick, (GCompareFunc) + nicklist_compare); + } + g_slist_free(nicklist); + + /* display the nicks */ + printformat(channel->server, channel->name, + MSGLEVEL_CRAP, TXT_NAMES, channel->name, ""); + display_sorted_nicks(channel, sorted); + g_slist_free(sorted); + + printformat(channel->server, channel->name, + MSGLEVEL_CRAP, TXT_ENDOFNAMES, + channel->name, nicks, ops, voices, normal); +} + +/* SYNTAX: NAMES [-ops -halfops -voices -normal] [ | **] */ +static void cmd_names(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + CHANNEL_REC *chanrec; + GHashTable *optlist; + GString *unknowns; + char *channel, **channels, **tmp; + int flags; + void *free_arg; + + g_return_if_fail(data != NULL); + if (!IS_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS, + "names", &optlist, &channel)) + return; + + if (strcmp(channel, "*") == 0 || *channel == '\0') { + if (!IS_CHANNEL(item)) + cmd_param_error(CMDERR_NOT_JOINED); + + channel = item->name; + } + + flags = 0; + if (g_hash_table_lookup(optlist, "ops") != NULL) + flags |= CHANNEL_NICKLIST_FLAG_OPS; + if (g_hash_table_lookup(optlist, "halfops") != NULL) + flags |= CHANNEL_NICKLIST_FLAG_HALFOPS; + if (g_hash_table_lookup(optlist, "voices") != NULL) + flags |= CHANNEL_NICKLIST_FLAG_VOICES; + if (g_hash_table_lookup(optlist, "normal") != NULL) + flags |= CHANNEL_NICKLIST_FLAG_NORMAL; + + if (flags == 0) flags = CHANNEL_NICKLIST_FLAG_ALL; + + unknowns = g_string_new(NULL); + + channels = g_strsplit(channel, ",", -1); + for (tmp = channels; *tmp != NULL; tmp++) { + chanrec = channel_find(server, *tmp); + if (chanrec == NULL) + g_string_sprintfa(unknowns, "%s,", *tmp); + else { + fe_channels_nicklist(chanrec, flags); + signal_stop(); + } + } + g_strfreev(channels); + + if (unknowns->len > 1) + g_string_truncate(unknowns, unknowns->len-1); + + if (unknowns->len > 0 && strcmp(channel, unknowns->str) != 0) + signal_emit("command names", 3, unknowns->str, server, item); + g_string_free(unknowns, TRUE); + + cmd_params_free(free_arg); +} + +/* SYNTAX: CYCLE [] [] */ +static void cmd_cycle(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + CHANNEL_REC *chanrec; + char *channame, *msg, *joindata; + void *free_arg; + + g_return_if_fail(data != NULL); + if (!IS_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN, + item, &channame, &msg)) + return; + if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = channel_find(server, channame); + if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); + + joindata = chanrec->get_join_data(chanrec); + window_bind_add(window_item_window(chanrec), + chanrec->server->tag, chanrec->name); + channel_destroy(chanrec); + + server->channels_join(server, joindata, FALSE); + g_free(joindata); + + cmd_params_free(free_arg); +} + +void fe_channels_init(void) +{ + settings_add_bool("lookandfeel", "autoclose_windows", TRUE); + settings_add_int("lookandfeel", "names_max_columns", 6); + settings_add_int("lookandfeel", "names_max_width", 0); + + signal_add("channel created", (SIGNAL_FUNC) signal_channel_created); + signal_add("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed); + signal_add_last("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy); + signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed); + signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected); + + command_bind_first("join", NULL, (SIGNAL_FUNC) cmd_wjoin_pre); + command_bind("join", NULL, (SIGNAL_FUNC) cmd_join); + command_bind_last("join", NULL, (SIGNAL_FUNC) cmd_wjoin_post); + command_bind("channel", NULL, (SIGNAL_FUNC) cmd_channel); + command_bind("channel add", NULL, (SIGNAL_FUNC) cmd_channel_add); + command_bind("channel remove", NULL, (SIGNAL_FUNC) cmd_channel_remove); + command_bind("channel list", NULL, (SIGNAL_FUNC) cmd_channel_list); + command_bind("names", NULL, (SIGNAL_FUNC) cmd_names); + command_bind("cycle", NULL, (SIGNAL_FUNC) cmd_cycle); + + command_set_options("channel add", "auto noauto -bots -botcmd"); + command_set_options("names", "ops halfops voices normal"); + command_set_options("join", "window"); +} + +void fe_channels_deinit(void) +{ + signal_remove("channel created", (SIGNAL_FUNC) signal_channel_created); + signal_remove("channel destroyed", (SIGNAL_FUNC) signal_channel_destroyed); + signal_remove("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy); + signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + + command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_pre); + command_unbind("join", (SIGNAL_FUNC) cmd_join); + command_unbind("join", (SIGNAL_FUNC) cmd_wjoin_post); + command_unbind("channel", (SIGNAL_FUNC) cmd_channel); + command_unbind("channel add", (SIGNAL_FUNC) cmd_channel_add); + command_unbind("channel remove", (SIGNAL_FUNC) cmd_channel_remove); + command_unbind("channel list", (SIGNAL_FUNC) cmd_channel_list); + command_unbind("names", (SIGNAL_FUNC) cmd_names); + command_unbind("cycle", (SIGNAL_FUNC) cmd_cycle); +} diff --git a/apps/irssi/src/fe-common/core/fe-channels.h b/apps/irssi/src/fe-common/core/fe-channels.h new file mode 100644 index 00000000..a8aa697e --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-channels.h @@ -0,0 +1,15 @@ +#ifndef __FE_CHANNELS_H +#define __FE_CHANNELS_H + +#define CHANNEL_NICKLIST_FLAG_OPS 0x01 +#define CHANNEL_NICKLIST_FLAG_HALFOPS 0x02 +#define CHANNEL_NICKLIST_FLAG_VOICES 0x04 +#define CHANNEL_NICKLIST_FLAG_NORMAL 0x08 +#define CHANNEL_NICKLIST_FLAG_ALL 0x0f + +void fe_channels_nicklist(CHANNEL_REC *channel, int flags); + +void fe_channels_init(void); +void fe_channels_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/fe-common-core.c b/apps/irssi/src/fe-common/core/fe-common-core.c new file mode 100644 index 00000000..24836905 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-common-core.c @@ -0,0 +1,359 @@ +/* + fe-common-core.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "args.h" +#include "misc.h" +#include "levels.h" +#include "settings.h" + +#include "channels.h" +#include "servers-setup.h" + +#include "fe-queries.h" +#include "hilight-text.h" +#include "command-history.h" +#include "completion.h" +#include "keyboard.h" +#include "printtext.h" +#include "formats.h" +#include "themes.h" +#include "translation.h" +#include "fe-channels.h" +#include "fe-windows.h" +#include "window-items.h" +#include "windows-layout.h" + +#include + +static char *autocon_server; +static char *autocon_password; +static int autocon_port; +static int no_autoconnect; +static char *cmdline_nick; +static char *cmdline_hostname; + +void autorun_init(void); +void autorun_deinit(void); + +void fe_core_log_init(void); +void fe_core_log_deinit(void); + +void fe_exec_init(void); +void fe_exec_deinit(void); + +void fe_expandos_init(void); +void fe_expandos_deinit(void); + +void fe_help_init(void); +void fe_help_deinit(void); + +void fe_ignore_init(void); +void fe_ignore_deinit(void); + +void fe_ignore_messages_init(void); +void fe_ignore_messages_deinit(void); + +void fe_log_init(void); +void fe_log_deinit(void); + +void fe_messages_init(void); +void fe_messages_deinit(void); + +void fe_modules_init(void); +void fe_modules_deinit(void); + +void fe_server_init(void); +void fe_server_deinit(void); + +void fe_settings_init(void); +void fe_settings_deinit(void); + +void window_activity_init(void); +void window_activity_deinit(void); + +void window_commands_init(void); +void window_commands_deinit(void); + +void fe_core_commands_init(void); +void fe_core_commands_deinit(void); + +static void sig_connected(SERVER_REC *server) +{ + MODULE_DATA_SET(server, g_new0(MODULE_SERVER_REC, 1)); +} + +static void sig_disconnected(SERVER_REC *server) +{ + g_free(MODULE_DATA(server)); +} + +static void sig_channel_created(CHANNEL_REC *channel) +{ + MODULE_DATA_SET(channel, g_new0(MODULE_CHANNEL_REC, 1)); +} + +static void sig_channel_destroyed(CHANNEL_REC *channel) +{ + g_free(MODULE_DATA(channel)); +} + +void fe_common_core_init(void) +{ + static struct poptOption options[] = { + { "connect", 'c', POPT_ARG_STRING, &autocon_server, 0, "Automatically connect to server/ircnet", "SERVER" }, + { "password", 'w', POPT_ARG_STRING, &autocon_password, 0, "Autoconnect password", "SERVER" }, + { "port", 'p', POPT_ARG_INT, &autocon_port, 0, "Autoconnect port", "PORT" }, + { "noconnect", '!', POPT_ARG_NONE, &no_autoconnect, 0, "Disable autoconnecting", NULL }, + /* + { "nick", 'n', POPT_ARG_STRING, &cmdline_nick, 0, "Specify nick to use", NULL }, + { "hostname", 'h', POPT_ARG_STRING, &cmdline_hostname, 0, "Specify host name to use", NULL }, + */ + { NULL, '\0', 0, NULL } + }; + + autocon_server = NULL; + autocon_password = NULL; + autocon_port = 6667; + no_autoconnect = FALSE; + cmdline_nick = NULL; + cmdline_hostname = NULL; + args_register(options); + + settings_add_bool("lookandfeel", "timestamps", TRUE); + settings_add_bool("lookandfeel", "msgs_timestamps", FALSE); + settings_add_int("lookandfeel", "timestamp_timeout", 0); + + settings_add_bool("lookandfeel", "bell_beeps", FALSE); + settings_add_str("lookandfeel", "beep_msg_level", ""); + settings_add_bool("lookandfeel", "beep_when_window_active", TRUE); + settings_add_bool("lookandfeel", "beep_when_away", TRUE); + + settings_add_bool("lookandfeel", "hide_text_style", FALSE); + settings_add_bool("lookandfeel", "hide_server_tags", FALSE); + + settings_add_bool("lookandfeel", "use_status_window", TRUE); + settings_add_bool("lookandfeel", "use_msgs_window", FALSE); + + themes_init(); + theme_register(fecommon_core_formats); + + autorun_init(); + command_history_init(); + completion_init(); + keyboard_init(); + printtext_init(); + formats_init(); +#ifndef WIN32 + fe_exec_init(); +#endif + fe_expandos_init(); + fe_help_init(); + fe_ignore_init(); + fe_log_init(); + fe_modules_init(); + fe_server_init(); + fe_settings_init(); + translation_init(); + windows_init(); + window_activity_init(); + window_commands_init(); + window_items_init(); + windows_layout_init(); + fe_core_commands_init(); + + fe_channels_init(); + fe_queries_init(); + + fe_messages_init(); + hilight_text_init(); + fe_ignore_messages_init(); + + settings_check(); + + signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); + signal_add_last("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add_first("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_add_last("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); +} + +void fe_common_core_deinit(void) +{ + autorun_deinit(); + hilight_text_deinit(); + command_history_deinit(); + completion_deinit(); + keyboard_deinit(); + printtext_deinit(); + formats_deinit(); +#ifndef WIN32 + fe_exec_deinit(); +#endif + fe_expandos_deinit(); + fe_help_deinit(); + fe_ignore_deinit(); + fe_log_deinit(); + fe_modules_deinit(); + fe_server_deinit(); + fe_settings_deinit(); + translation_deinit(); + windows_deinit(); + window_activity_deinit(); + window_commands_deinit(); + window_items_deinit(); + windows_layout_deinit(); + fe_core_commands_deinit(); + + fe_channels_deinit(); + fe_queries_deinit(); + + fe_messages_deinit(); + fe_ignore_messages_init(); + + theme_unregister(); + themes_deinit(); + + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("channel created", (SIGNAL_FUNC) sig_channel_created); + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); +} + +void glog_func(const char *log_domain, GLogLevelFlags log_level, + const char *message) +{ + const char *reason; + + switch (log_level) { + case G_LOG_LEVEL_WARNING: + reason = "warning"; + break; + case G_LOG_LEVEL_CRITICAL: + reason = "critical"; + break; + default: + reason = "error"; + break; + } + + if (windows == NULL) + fprintf(stderr, "GLib %s: %s", reason, message); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_GLIB_ERROR, reason, message); + } +} + +static void create_windows(void) +{ + WINDOW_REC *window; + + windows_layout_restore(); + if (windows != NULL) + return; + + if (settings_get_bool("use_status_window")) { + window = window_create(NULL, TRUE); + window_set_name(window, "(status)"); + window_set_level(window, MSGLEVEL_ALL ^ + (settings_get_bool("use_msgs_window") ? + (MSGLEVEL_MSGS|MSGLEVEL_DCCMSGS) : 0)); + } + + if (settings_get_bool("use_msgs_window")) { + window = window_create(NULL, TRUE); + window_set_name(window, "(msgs)"); + window_set_level(window, MSGLEVEL_MSGS|MSGLEVEL_DCCMSGS); + } + + if (windows == NULL) { + /* we have to have at least one window.. */ + window = window_create(NULL, TRUE); + } +} + +static void autoconnect_servers(void) +{ + GSList *tmp, *chatnets; + char *str; + + if (autocon_server != NULL) { + /* connect to specified server */ + str = g_strdup_printf(autocon_password == NULL ? "%s %d" : "%s %d %s", + autocon_server, autocon_port, autocon_password); + signal_emit("command connect", 1, str); + g_free(str); + return; + } + + if (no_autoconnect) { + /* don't autoconnect */ + return; + } + + /* connect to autoconnect servers */ + chatnets = NULL; + for (tmp = setupservers; tmp != NULL; tmp = tmp->next) { + SERVER_SETUP_REC *rec = tmp->data; + + if (rec->autoconnect && + (rec->chatnet == NULL || + gslist_find_icase_string(chatnets, rec->chatnet) == NULL)) { + if (rec->chatnet != NULL) + chatnets = g_slist_append(chatnets, rec->chatnet); + + str = g_strdup_printf("%s %d", rec->address, rec->port); + signal_emit("command connect", 1, str); + g_free(str); + } + } + + g_slist_free(chatnets); +} + +void fe_common_core_finish_init(void) +{ + signal_emit("irssi init read settings", 0); + +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + if (cmdline_nick != NULL) { + /* override nick found from setup */ + settings_set_str("nick", cmdline_nick); + } + + if (cmdline_hostname != NULL) { + /* override host name found from setup */ + settings_set_str("hostname", cmdline_hostname); + } + + create_windows(); + + /* _after_ windows are created.. */ + g_log_set_handler(G_LOG_DOMAIN, + (GLogLevelFlags) (G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_WARNING), + (GLogFunc) glog_func, NULL); + + autoconnect_servers(); +} diff --git a/apps/irssi/src/fe-common/core/fe-common-core.h b/apps/irssi/src/fe-common/core/fe-common-core.h new file mode 100644 index 00000000..1c12047b --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-common-core.h @@ -0,0 +1,8 @@ +#ifndef __FE_COMMON_CORE_H +#define __FE_COMMON_CORE_H + +void fe_common_core_init(void); +void fe_common_core_deinit(void); +void fe_common_core_finish_init(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/fe-core-commands.c b/apps/irssi/src/fe-common/core/fe-core-commands.c new file mode 100644 index 00000000..7b8f7f9b --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-core-commands.c @@ -0,0 +1,303 @@ +/* + fe-core-commands.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "line-split.h" +#include "settings.h" +#include "irssi-version.h" + +#include "fe-windows.h" +#include "printtext.h" + +#define PASTE_CHECK_SPEED 200 /* 0.2 sec */ + +static int ret_texts[] = { + TXT_OPTION_UNKNOWN, + TXT_OPTION_AMBIGUOUS, + TXT_OPTION_MISSING_ARG, + TXT_COMMAND_UNKNOWN, + TXT_COMMAND_AMBIGUOUS, + -1, + TXT_NOT_ENOUGH_PARAMS, + TXT_NOT_CONNECTED, + TXT_NOT_JOINED, + TXT_CHAN_NOT_FOUND, + TXT_CHAN_NOT_SYNCED, + TXT_NOT_GOOD_IDEA +}; + +/* keep the whole command line here temporarily. we need it in + "default command" event handler, but there we don't know if the start of + the line had one or two command chars, and which one.. */ +static const char *current_cmdline; +static int hide_output; + +static GTimeVal time_command_last, time_command_now; +static int last_command_cmd, command_cmd; + +/* SYNTAX: ECHO [-current] [-window ] [-level ] */ +static void cmd_echo(const char *data, void *server, WI_ITEM_REC *item) +{ + WINDOW_REC *window; + GHashTable *optlist; + char *msg, *levelstr, *winname; + void *free_arg; + int level; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST, "echo", &optlist, &msg)) + return; + + levelstr = g_hash_table_lookup(optlist, "level"); + level = levelstr == NULL ? 0 : + level2bits(g_hash_table_lookup(optlist, "level")); + if (level == 0) level = MSGLEVEL_CRAP; + + winname = g_hash_table_lookup(optlist, "window"); + window = winname == NULL ? NULL : + is_numeric(winname, '\0') ? + window_find_refnum(atoi(winname)) : + window_find_item(NULL, winname); + if (window == NULL) window = active_win; + + printtext_window(window, level, "%s", msg); + cmd_params_free(free_arg); +} + +/* SYNTAX: VERSION */ +static void cmd_version(char *data) +{ + g_return_if_fail(data != NULL); + + if (*data == '\0') { + printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + "Client: "PACKAGE" " IRSSI_VERSION); + } +} + +/* SYNTAX: CAT */ +static void cmd_cat(const char *data) +{ + LINEBUF_REC *buffer = NULL; + char *fname, *fposstr; + char tmpbuf[1024], *str; + void *free_arg; + int f, ret, recvlen, fpos; + + if (!cmd_get_params(data, &free_arg, 2, &fname, &fposstr)) + return; + + fname = convert_home(fname); + fpos = atoi(fposstr); + cmd_params_free(free_arg); + + f = open(fname, O_RDONLY); + g_free(fname); + + if (f == -1) { + /* file not found */ + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "%s", g_strerror(errno)); + return; + } + + lseek(f, fpos, SEEK_SET); + do { + recvlen = read(f, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, &buffer); + if (ret > 0) + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s", str); + } while (ret > 0); + line_split_free(buffer); + + close(f); +} + +/* SYNTAX: BEEP */ +static void cmd_beep(void) +{ + signal_emit("beep", 0); +} + +static void sig_stop(void) +{ + signal_stop(); +} + +static void event_command(const char *data) +{ + const char *cmdchar; + + /* save current command line */ + current_cmdline = data; + + /* for detecting if we're pasting text */ + time_command_last = time_command_now; + last_command_cmd = command_cmd; + + g_get_current_time(&time_command_now); + command_cmd = strchr(settings_get_str("cmdchars"), *data) != NULL; + + /* /^command hides the output of the command */ + cmdchar = strchr(settings_get_str("cmdchars"), *data); + if (cmdchar != NULL && (data[1] == '^' || + (data[1] == *cmdchar && data[2] == '^'))) { + hide_output = TRUE; + signal_add_first("print starting", (SIGNAL_FUNC) sig_stop); + signal_add_first("print format", (SIGNAL_FUNC) sig_stop); + signal_add_first("print text stripped", (SIGNAL_FUNC) sig_stop); + signal_add_first("print text", (SIGNAL_FUNC) sig_stop); + } +} + +static void event_command_last(const char *data) +{ + if (hide_output) { + hide_output = FALSE; + signal_remove("print starting", (SIGNAL_FUNC) sig_stop); + signal_remove("print format", (SIGNAL_FUNC) sig_stop); + signal_remove("print text stripped", (SIGNAL_FUNC) sig_stop); + signal_remove("print text", (SIGNAL_FUNC) sig_stop); + } +} + +static void event_default_command(const char *data, void *server, + WI_ITEM_REC *item) +{ + const char *cmdchars, *ptr; + char *cmd, *p; + long diff; + + cmdchars = settings_get_str("cmdchars"); + + ptr = data; + while (*ptr != '\0' && *ptr != ' ') { + if (strchr(cmdchars, *ptr)) { + /* command character inside command .. we probably + want to send this text to channel. for example + when pasting a path /usr/bin/xxx. */ + signal_emit("send text", 3, current_cmdline, server, item); + return; + } + ptr++; + } + + /* maybe we're copy+pasting text? check how long it was since the + last line */ + diff = get_timeval_diff(&time_command_now, &time_command_last); + if (item != NULL && !last_command_cmd && diff < PASTE_CHECK_SPEED) { + signal_emit("send text", 3, current_cmdline, active_win->active_server, active_win->active); + command_cmd = FALSE; + return; + } + + /* get the command part of the line, send "error command" signal */ + cmd = g_strdup(data); + p = strchr(cmd, ' '); + if (p != NULL) *p = '\0'; + + signal_emit("error command", 2, GINT_TO_POINTER(CMDERR_UNKNOWN), cmd); + + g_free(cmd); +} + +static void event_cmderror(void *errorp, const char *arg) +{ + int error; + + error = GPOINTER_TO_INT(errorp); + if (error == CMDERR_ERRNO) { + /* errno is special */ + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", g_strerror(errno)); + } else { + /* others */ + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, ret_texts[error + -CMDERR_OPTION_UNKNOWN], arg); + } +} + +static void event_list_subcommands(const char *command) +{ + GSList *tmp; + GString *str; + int len; + + str = g_string_new(NULL); + + len = strlen(command); + for (tmp = commands; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + if (g_strncasecmp(rec->cmd, command, len) == 0 && + rec->cmd[len] == ' ' && + strchr(rec->cmd+len+1, ' ') == NULL) { + g_string_sprintfa(str, "%s ", rec->cmd+len+1); + } + } + + if (str->len != 0) { + g_string_truncate(str, str->len-1); + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str->str); + } + + g_string_free(str, TRUE); +} + +void fe_core_commands_init(void) +{ + hide_output = FALSE; + + command_cmd = FALSE; + memset(&time_command_now, 0, sizeof(GTimeVal)); + + command_bind("echo", NULL, (SIGNAL_FUNC) cmd_echo); + command_bind("version", NULL, (SIGNAL_FUNC) cmd_version); + command_bind("cat", NULL, (SIGNAL_FUNC) cmd_cat); + command_bind("beep", NULL, (SIGNAL_FUNC) cmd_beep); + + signal_add("send command", (SIGNAL_FUNC) event_command); + signal_add_last("send command", (SIGNAL_FUNC) event_command_last); + signal_add("default command", (SIGNAL_FUNC) event_default_command); + signal_add("error command", (SIGNAL_FUNC) event_cmderror); + signal_add("list subcommands", (SIGNAL_FUNC) event_list_subcommands); + + command_set_options("echo", "current +level +window"); +} + +void fe_core_commands_deinit(void) +{ + command_unbind("echo", (SIGNAL_FUNC) cmd_echo); + command_unbind("version", (SIGNAL_FUNC) cmd_version); + command_unbind("cat", (SIGNAL_FUNC) cmd_cat); + command_unbind("beep", (SIGNAL_FUNC) cmd_beep); + + signal_remove("send command", (SIGNAL_FUNC) event_command); + signal_remove("send command", (SIGNAL_FUNC) event_command_last); + signal_remove("default command", (SIGNAL_FUNC) event_default_command); + signal_remove("error command", (SIGNAL_FUNC) event_cmderror); + signal_remove("list subcommands", (SIGNAL_FUNC) event_list_subcommands); +} diff --git a/apps/irssi/src/fe-common/core/fe-exec.c b/apps/irssi/src/fe-common/core/fe-exec.c new file mode 100644 index 00000000..bc8f4691 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-exec.c @@ -0,0 +1,641 @@ +/* + fe-exec.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "pidwait.h" +#include "line-split.h" +#include "net-sendbuffer.h" +#include "misc.h" +#include "levels.h" + +#include "printtext.h" +#include "fe-exec.h" +#include "fe-windows.h" +#include "window-items.h" + +#include +#include + +static GSList *processes; +static int signal_exec_input; + +static EXEC_WI_REC *exec_wi_create(WINDOW_REC *window, PROCESS_REC *rec) +{ + EXEC_WI_REC *item; + + g_return_val_if_fail(window != NULL, NULL); + g_return_val_if_fail(rec != NULL, NULL); + + item = g_new0(EXEC_WI_REC, 1); + item->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "EXEC"); + item->name = rec->name != NULL ? + g_strdup_printf("%%%s", rec->name) : + g_strdup_printf("%%%d", rec->id); + + item->window = window; + item->createtime = time(NULL); + item->process = rec; + + MODULE_DATA_INIT(item); + window_item_add(window, (WI_ITEM_REC *) item, FALSE); + return item; +} + +static void exec_wi_destroy(EXEC_WI_REC *rec) +{ + g_return_if_fail(rec != NULL); + + if (rec->destroying) return; + rec->destroying = TRUE; + + if (window_item_window((WI_ITEM_REC *) rec) != NULL) + window_item_destroy((WI_ITEM_REC *) rec); + + MODULE_DATA_DEINIT(rec); + g_free(rec->name); + g_free(rec); +} + +static int process_get_new_id(void) +{ + PROCESS_REC *rec; + GSList *tmp; + int id; + + id = 0; + tmp = processes; + while (tmp != NULL) { + rec = tmp->data; + + if (id != rec->id) { + tmp = tmp->next; + continue; + } + + id++; + tmp = processes; + } + + return id; +} + +static PROCESS_REC *process_find_pid(int pid) +{ + GSList *tmp; + + g_return_val_if_fail(pid > 0, NULL); + + for (tmp = processes; tmp != NULL; tmp = tmp->next) { + PROCESS_REC *rec = tmp->data; + + if (rec->pid == pid) + return rec; + } + + return NULL; +} + +static PROCESS_REC *process_find_id(int id, int verbose) +{ + GSList *tmp; + + g_return_val_if_fail(id != -1, NULL); + + for (tmp = processes; tmp != NULL; tmp = tmp->next) { + PROCESS_REC *rec = tmp->data; + + if (rec->id == id) + return rec; + } + + if (verbose) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Unknown process id: %d", id); + } + + return NULL; +} + +static PROCESS_REC *process_find(const char *name, int verbose) +{ + GSList *tmp; + + g_return_val_if_fail(name != NULL, NULL); + + if (*name == '%' && is_numeric(name+1, 0)) + return process_find_id(atoi(name+1), verbose); + + for (tmp = processes; tmp != NULL; tmp = tmp->next) { + PROCESS_REC *rec = tmp->data; + + if (rec->name != NULL && strcmp(rec->name, name) == 0) + return rec; + } + + if (verbose) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Unknown process name: %s", name); + } + + return NULL; +} + +static void process_destroy(PROCESS_REC *rec, int status) +{ + processes = g_slist_remove(processes, rec); + + signal_emit("exec remove", 2, rec, GINT_TO_POINTER(status)); + + if (rec->read_tag != -1) + g_source_remove(rec->read_tag); + if (rec->target_item != NULL) + exec_wi_destroy(rec->target_item); + + line_split_free(rec->databuf); + g_io_channel_close(rec->in); + g_io_channel_unref(rec->in); + net_sendbuffer_destroy(rec->out, TRUE); + + g_free_not_null(rec->name); + g_free_not_null(rec->target); + g_free(rec->args); + g_free(rec); +} + +static void processes_killall(int signum) +{ + GSList *tmp; + + for (tmp = processes; tmp != NULL; tmp = tmp->next) { + PROCESS_REC *rec = tmp->data; + + kill(rec->pid, signum); + } +} + +static int signal_name_to_id(const char *name) +{ + /* check only the few most common signals, too much job to check + them all. if we sometimes want more, procps-sources/proc/sig.c + would be useful for copypasting */ + if (g_strcasecmp(name, "hup") == 0) + return SIGHUP; + if (g_strcasecmp(name, "int") == 0) + return SIGINT; + if (g_strcasecmp(name, "term") == 0) + return SIGTERM; + if (g_strcasecmp(name, "kill") == 0) + return SIGKILL; + if (g_strcasecmp(name, "usr1") == 0) + return SIGUSR1; + if (g_strcasecmp(name, "usr2") == 0) + return SIGUSR2; + return -1; +} + +/* `optlist' should contain only one unknown key - the server tag. + returns NULL if there was unknown -option */ +static int cmd_options_get_signal(const char *cmd, + GHashTable *optlist) +{ + GSList *list, *tmp, *next; + char *signame; + int signum; + + /* get all the options, then remove the known ones. there should + be only one left - the signal */ + list = hashtable_get_keys(optlist); + if (cmd != NULL) { + for (tmp = list; tmp != NULL; tmp = next) { + char *option = tmp->data; + next = tmp->next; + + if (command_have_option(cmd, option)) + list = g_slist_remove(list, option); + } + } + + if (list == NULL) + return -1; + + signame = list->data; + signum = -1; + + signum = is_numeric(signame, 0) ? atol(signame) : + signal_name_to_id(signame); + + if (signum == -1 || list->next != NULL) { + /* unknown option (not a signal) */ + signal_emit("error command", 2, + GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN), + signum == -1 ? list->data : list->next->data); + signal_stop(); + return -2; + } + + g_slist_free(list); + return signum; +} + +static void exec_show_list(void) +{ + GSList *tmp; + + for (tmp = processes; tmp != NULL; tmp = tmp->next) { + PROCESS_REC *rec = tmp->data; + + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, + "%d (%s): %s", rec->id, rec->name, rec->args); + } +} + +static void process_exec(PROCESS_REC *rec, const char *cmd) +{ + const char *shell_args[4] = { "/bin/sh", "-c", NULL, NULL }; + char **args; + int in[2], out[2]; + int n; + + if (pipe(in) == -1) + return; + if (pipe(out) == -1) + return; + + shell_args[2] = cmd; + rec->pid = fork(); + if (rec->pid == -1) { + /* error */ + close(in[0]); close(in[1]); + close(out[0]); close(out[1]); + return; + } + + if (rec->pid != 0) { + /* parent process */ + GIOChannel *outio = g_io_channel_unix_new(in[1]); + + rec->in = g_io_channel_unix_new(out[0]); + rec->out = net_sendbuffer_create(outio, 0); + + close(out[1]); + close(in[0]); + pidwait_add(rec->pid); + return; + } + + /* child process, try to clean up everything */ + setsid(); + setuid(getuid()); + setgid(getgid()); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_DFL); + + putenv("TERM=tty"); + + /* set stdin, stdout and stderr */ + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(out[1], STDERR_FILENO); + + /* don't let child see our files */ + for (n = 3; n < 256; n++) + close(n); + + if (rec->shell) { + execvp(shell_args[0], (char **) shell_args); + + fprintf(stderr, "Exec: /bin/sh: %s\n", g_strerror(errno)); + } else { + args = g_strsplit(cmd, " ", -1); + execvp(args[0], args); + + fprintf(stderr, "Exec: %s: %s\n", args[0], g_strerror(errno)); + } + + _exit(-1); +} + +static void sig_exec_input_reader(PROCESS_REC *rec) +{ + char tmpbuf[512], *str; + unsigned int recvlen; + int err, ret; + + g_return_if_fail(rec != NULL); + + recvlen = 0; + err = g_io_channel_read(rec->in, tmpbuf, + sizeof(tmpbuf), &recvlen); + if (err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR) + recvlen = -1; + + do { + ret = line_split(tmpbuf, recvlen, &str, &rec->databuf); + if (ret == -1) { + /* link to terminal closed? */ + g_source_remove(rec->read_tag); + rec->read_tag = -1; + break; + } + + if (ret > 0) { + signal_emit_id(signal_exec_input, 2, rec, str); + if (recvlen > 0) recvlen = 0; + } + } while (ret > 0); +} + +static void handle_exec(const char *args, GHashTable *optlist, + WI_ITEM_REC *item) +{ + PROCESS_REC *rec; + char *target; + int notice, signum, interactive; + + /* check that there's no unknown options. we allowed them + because signals can be used as options, but there should be + only one unknown option: the signal name/number. */ + signum = cmd_options_get_signal("exec", optlist); + if (signum == -2) + return; + + if (*args == '\0') { + exec_show_list(); + return; + } + + target = NULL; + notice = FALSE; + + if (g_hash_table_lookup(optlist, "in") != NULL) { + rec = process_find(g_hash_table_lookup(optlist, "in"), TRUE); + if (rec != NULL) { + net_sendbuffer_send(rec->out, args, strlen(args)); + net_sendbuffer_send(rec->out, "\n", 1); + } + return; + } + + /* check if args is a process ID or name. if it's ID but not found, + complain about it and fail immediately */ + rec = process_find(args, *args == '%'); + if (*args == '%' && rec == NULL) + return; + + /* common options */ + if (g_hash_table_lookup(optlist, "out") != NULL) { + /* redirect output to active channel/query */ + if (item == NULL) + cmd_return_error(CMDERR_NOT_JOINED); + target = item->name; + } else if (g_hash_table_lookup(optlist, "msg") != NULL) { + /* redirect output to /msg */ + target = g_hash_table_lookup(optlist, "msg"); + } else if (g_hash_table_lookup(optlist, "notice") != NULL) { + target = g_hash_table_lookup(optlist, "notice"); + notice = TRUE; + } + + /* options that require process ID/name as argument */ + if (rec == NULL && + (signum != -1 || g_hash_table_lookup(optlist, "close") != NULL)) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Unknown process name: %s", args); + return; + } + if (g_hash_table_lookup(optlist, "close") != NULL) { + /* forcibly close the process */ + process_destroy(rec, -1); + return; + } + + if (signum != -1) { + /* send a signal to process */ + kill(rec->pid, signum); + return; + } + + interactive = g_hash_table_lookup(optlist, "interactive") != NULL; + if (*args == '%') { + /* do something to already existing process */ + char *name; + + if (target != NULL) { + /* redirect output to target */ + g_free_and_null(rec->target); + rec->target = g_strdup(target); + rec->notice = notice; + } + + name = g_hash_table_lookup(optlist, "name"); + if (name != NULL) { + /* change window name */ + g_free_not_null(rec->name); + rec->name = *name == '\0' ? NULL : g_strdup(name); + } else if (target == NULL && + (rec->target_item == NULL || interactive)) { + /* no parameters given, + redirect output to the active window */ + g_free_and_null(rec->target); + rec->target_win = active_win; + + if (rec->target_item != NULL) { + exec_wi_destroy(rec->target_item); + rec->target_item = NULL; + } + + if (interactive) { + rec->target_item = + exec_wi_create(active_win, rec); + } + } + return; + } + + /* starting a new process */ + rec = g_new0(PROCESS_REC, 1); + rec->pid = -1; + rec->shell = g_hash_table_lookup(optlist, "nosh") == NULL; + + process_exec(rec, args); + if (rec->pid == -1) { + /* pipe() or fork() failed */ + g_free(rec); + cmd_return_error(CMDERR_ERRNO); + } + + rec->id = process_get_new_id(); + rec->target = g_strdup(target); + rec->target_win = active_win; + rec->args = g_strdup(args); + rec->notice = notice; + rec->silent = g_hash_table_lookup(optlist, "-") != NULL; + rec->name = g_strdup(g_hash_table_lookup(optlist, "name")); + + rec->read_tag = g_input_add(rec->in, G_INPUT_READ, + (GInputFunction) sig_exec_input_reader, + rec); + processes = g_slist_append(processes, rec); + + if (rec->target == NULL && interactive) + rec->target_item = exec_wi_create(active_win, rec); + + signal_emit("exec new", 1, rec); +} + +/* SYNTAX: EXEC [-] [-nosh] [-out | -msg | -notice ] + [-name ] + EXEC -out | -window | -msg | -notice | + -close | - + EXEC -in */ +static void cmd_exec(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + GHashTable *optlist; + char *args; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, + "exec", &optlist, &args)) { + handle_exec(args, optlist, item); + cmd_params_free(free_arg); + } +} + +static void sig_pidwait(void *pid, void *statusp) +{ + PROCESS_REC *rec; + int status = GPOINTER_TO_INT(statusp); + + rec = process_find_pid(GPOINTER_TO_INT(pid)); + if (rec == NULL) return; + + /* process exited */ + if (!rec->silent) { + if (WIFSIGNALED(status)) { + status = WTERMSIG(status); + printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + "process %d (%s) terminated with signal %d (%s)", + rec->id, rec->args, + status, g_strsignal(status)); + } else { + status = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + "process %d (%s) terminated with return code %d", + rec->id, rec->args, status); + } + } + process_destroy(rec, status); +} + +static void sig_exec_input(PROCESS_REC *rec, const char *text) +{ + WI_ITEM_REC *item; + SERVER_REC *server; + char *str; + + item = NULL; + server = NULL; + + if (rec->target != NULL) { + item = window_item_find(NULL, rec->target); + server = item != NULL ? item->server : + active_win->active_server; + + str = g_strconcat(rec->target, " ", text, NULL); + signal_emit(rec->notice ? "command notice" : "command msg", + 3, str, server, item); + g_free(str); + } else if (rec->target_item != NULL) { + printtext(NULL, rec->target_item->name, MSGLEVEL_CLIENTCRAP, + "%s", text); + } else { + printtext_window(rec->target_win, MSGLEVEL_CLIENTCRAP, + "%s", text); + } +} + +static void sig_window_destroyed(WINDOW_REC *window) +{ + GSList *tmp; + + /* window is being closed, if there's any /exec targets for it, + change them to active window. */ + for (tmp = processes; tmp != NULL; tmp = tmp->next) { + PROCESS_REC *rec = tmp->data; + + if (rec->target_win == window) + rec->target_win = active_win; + } +} + +static void sig_window_item_destroyed(WINDOW_REC *window, EXEC_WI_REC *item) +{ + if (IS_EXEC_WI(item)) { + item->process->target_item = NULL; + exec_wi_destroy(item); + } +} + +static void event_text(const char *data, SERVER_REC *server, EXEC_WI_REC *item) +{ + if (!IS_EXEC_WI(item)) return; + + net_sendbuffer_send(item->process->out, data, strlen(data)); + net_sendbuffer_send(item->process->out, "\n", 1); + signal_stop(); +} + +void fe_exec_init(void) +{ + command_bind("exec", NULL, (SIGNAL_FUNC) cmd_exec); + command_set_options("exec", "!- interactive nosh +name out +msg +notice +in window close"); + + signal_exec_input = signal_get_uniq_id("exec input"); + signal_add("pidwait", (SIGNAL_FUNC) sig_pidwait); + signal_add("exec input", (SIGNAL_FUNC) sig_exec_input); + signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed); + signal_add("window item destroy", (SIGNAL_FUNC) sig_window_item_destroyed); + signal_add_first("send text", (SIGNAL_FUNC) event_text); +} + +void fe_exec_deinit(void) +{ + if (processes != NULL) { + processes_killall(SIGTERM); + sleep(1); + processes_killall(SIGKILL); + + while (processes != NULL) + process_destroy(processes->data, -1); + } + + command_unbind("exec", (SIGNAL_FUNC) cmd_exec); + + signal_remove("pidwait", (SIGNAL_FUNC) sig_pidwait); + signal_remove("exec input", (SIGNAL_FUNC) sig_exec_input); + signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed); + signal_remove("window item destroy", (SIGNAL_FUNC) sig_window_item_destroyed); + signal_remove("send text", (SIGNAL_FUNC) event_text); +} diff --git a/apps/irssi/src/fe-common/core/fe-exec.h b/apps/irssi/src/fe-common/core/fe-exec.h new file mode 100644 index 00000000..7f569ece --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-exec.h @@ -0,0 +1,45 @@ +#ifndef __FE_EXEC_H +#define __FE_EXEC_H + +#include "fe-windows.h" + +#define EXEC_WI(query) \ + MODULE_CHECK_CAST_MODULE(query, EXEC_WI_REC, type, \ + "WINDOW ITEM TYPE", "EXEC") + +#define IS_EXEC_WI(query) \ + (EXEC_WI(query) ? TRUE : FALSE) + +typedef struct PROCESS_REC PROCESS_REC; + +#define STRUCT_SERVER_REC void +typedef struct { +#include "window-item-rec.h" + PROCESS_REC *process; + unsigned int destroying:1; +} EXEC_WI_REC; + +struct PROCESS_REC { + int id; + char *name; + char *args; + + int pid; + GIOChannel *in; + NET_SENDBUF_REC *out; + LINEBUF_REC *databuf; + int read_tag; + + char *target; /* send text with /msg ... */ + WINDOW_REC *target_win; /* print text to this window */ + EXEC_WI_REC *target_item; /* print text to this exec window item */ + + unsigned int shell:1; /* start the program via /bin/sh */ + unsigned int notice:1; /* send text with /notice, not /msg if target is set */ + unsigned int silent:1; /* don't print "process exited with level xx" */ +}; + +void fe_exec_init(void); +void fe_exec_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/fe-expandos.c b/apps/irssi/src/fe-common/core/fe-expandos.c new file mode 100644 index 00000000..7eb145db --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-expandos.c @@ -0,0 +1,51 @@ +/* + fe-expandos.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "expandos.h" +#include "fe-windows.h" + +/* Window ref# */ +static char *expando_winref(SERVER_REC *server, void *item, int *free_ret) +{ + *free_ret = TRUE; + return g_strdup_printf("%d", active_win->refnum); +} + +/* Window name */ +static char *expando_winname(SERVER_REC *server, void *item, int *free_ret) +{ + return active_win->name; +} + +void fe_expandos_init(void) +{ + expando_create("winref", expando_winref, + "window changed", EXPANDO_ARG_NONE, + "window refnum changed", EXPANDO_ARG_WINDOW, NULL); + expando_create("winname", expando_winname, + "window name changed", EXPANDO_ARG_WINDOW, NULL); +} + +void fe_expandos_deinit(void) +{ + expando_destroy("winref", expando_winref); + expando_destroy("winname", expando_winname); +} diff --git a/apps/irssi/src/fe-common/core/fe-help.c b/apps/irssi/src/fe-common/core/fe-help.c new file mode 100644 index 00000000..fa90473d --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-help.c @@ -0,0 +1,256 @@ +/* + fe-help.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "line-split.h" +#include "settings.h" + +#include "printtext.h" +#include "formats.h" + +static int commands_equal(COMMAND_REC *rec, COMMAND_REC *rec2) +{ + if (rec->category == NULL && rec2->category != NULL) + return -1; + if (rec2->category == NULL && rec->category != NULL) + return 1; + + return strcmp(rec->cmd, rec2->cmd); +} + +static int get_cmd_length(void *data) +{ + return strlen(((COMMAND_REC *) data)->cmd); +} + +static void help_category(GSList *cmdlist, int items) +{ + WINDOW_REC *window; + TEXT_DEST_REC dest; + GString *str; + GSList *tmp; + int *columns, cols, rows, col, row, last_col_rows, max_width; + char *linebuf, *format, *stripped; + + window = window_find_closest(NULL, NULL, MSGLEVEL_CLIENTCRAP); + max_width = window->width; + + /* remove width of timestamp from max_width */ + format_create_dest(&dest, NULL, NULL, MSGLEVEL_CLIENTCRAP, NULL); + format = format_get_line_start(current_theme, &dest, time(NULL)); + if (format != NULL) { + stripped = strip_codes(format); + max_width -= strlen(stripped); + g_free(stripped); + g_free(format); + } + + /* calculate columns */ + cols = get_max_column_count(cmdlist, get_cmd_length, + max_width, 6, 1, 3, &columns, &rows); + cmdlist = columns_sort_list(cmdlist, rows); + + /* rows in last column */ + last_col_rows = rows-(cols*rows-g_slist_length(cmdlist)); + if (last_col_rows == 0) + last_col_rows = rows; + + str = g_string_new(NULL); + linebuf = g_malloc(max_width+1); + + col = 0; row = 0; + for (tmp = cmdlist; tmp != NULL; tmp = tmp->next) { + COMMAND_REC *rec = tmp->data; + + memset(linebuf, ' ', columns[col]); + linebuf[columns[col]] = '\0'; + memcpy(linebuf, rec->cmd, strlen(rec->cmd)); + g_string_append(str, linebuf); + + if (++col == cols) { + printtext(NULL, NULL, + MSGLEVEL_CLIENTCRAP, "%s", str->str); + g_string_truncate(str, 0); + col = 0; row++; + + if (row == last_col_rows) + cols--; + } + } + if (str->len != 0) + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s", str->str); + + g_slist_free(cmdlist); + g_string_free(str, TRUE); + g_free(columns); + g_free(linebuf); +} + +static int show_help_file(const char *file) +{ + const char *helppath; + char tmpbuf[1024], *str, *path; + LINEBUF_REC *buffer = NULL; + int f, ret, recvlen; + + helppath = settings_get_str("help_path"); + + /* helpdir/command or helpdir/category/command */ + path = g_strdup_printf("%s/%s", helppath, file); + f = open(path, O_RDONLY); + g_free(path); + + if (f == -1) + return FALSE; + + /* just print to screen whatever is in the file */ + do { + recvlen = read(f, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, &buffer); + if (ret > 0) { + str = g_strconcat("%|", str, NULL); + printtext_string(NULL, NULL, MSGLEVEL_CLIENTCRAP, str); + g_free(str); + } + } + while (ret > 0); + line_split_free(buffer); + + close(f); + return TRUE; +} + +static void show_help(const char *data) +{ + COMMAND_REC *rec, *last; + GSList *tmp, *cmdlist; + int items, findlen; + int header, found, fullmatch; + + g_return_if_fail(data != NULL); + + /* sort the commands list */ + commands = g_slist_sort(commands, (GCompareFunc) commands_equal); + + /* print command, sort by category */ + cmdlist = NULL; last = NULL; header = FALSE; fullmatch = FALSE; + items = 0; findlen = strlen(data); found = FALSE; + for (tmp = commands; tmp != NULL; last = rec, tmp = tmp->next) { + rec = tmp->data; + + if (last != NULL && rec->category != NULL && + (last->category == NULL || + strcmp(rec->category, last->category) != 0)) { + /* category changed */ + if (items > 0) { + if (!header) { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "Irssi commands:"); + header = TRUE; + } + if (last->category != NULL) { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, ""); + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s:", last->category); + } + help_category(cmdlist, items); + } + + g_slist_free(cmdlist); cmdlist = NULL; + items = 0; + } + + if (last != NULL && g_strcasecmp(rec->cmd, last->cmd) == 0) + continue; /* don't display same command twice */ + + if ((int)strlen(rec->cmd) >= findlen && + g_strncasecmp(rec->cmd, data, findlen) == 0) { + if (rec->cmd[findlen] == '\0') { + fullmatch = TRUE; + found = TRUE; + break; + } + else if (strchr(rec->cmd+findlen+1, ' ') == NULL) { + /* not a subcommand (and matches the query) */ + items++; + cmdlist = g_slist_append(cmdlist, rec); + found = TRUE; + } + } + } + + if ((!found || fullmatch) && !show_help_file(data)) { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, + "No help for %s", data); + } + + if (*data != '\0' && data[strlen(data)-1] != ' ' && + command_have_sub(data)) { + char *cmd; + + cmd = g_strconcat(data, " ", NULL); + show_help(cmd); + g_free(cmd); + } + + if (items != 0) { + /* display the last category */ + if (!header) { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, + "Irssi commands:"); + header = TRUE; + } + + if (last->category != NULL) { + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, ""); + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, + "%s:", last->category); + } + help_category(cmdlist, items); + g_slist_free(cmdlist); + } +} + +/* SYNTAX: HELP [] */ +static void cmd_help(const char *data) +{ + char *cmd, *ptr; + + cmd = g_strdup(data); + ptr = cmd+strlen(cmd); + while (ptr[-1] == ' ') ptr--; *ptr = '\0'; + + show_help(cmd); + g_free(cmd); +} + +void fe_help_init(void) +{ + settings_add_str("misc", "help_path", HELPDIR); + command_bind("help", NULL, (SIGNAL_FUNC) cmd_help); +} + +void fe_help_deinit(void) +{ + command_unbind("help", (SIGNAL_FUNC) cmd_help); +} diff --git a/apps/irssi/src/fe-common/core/fe-ignore-messages.c b/apps/irssi/src/fe-common/core/fe-ignore-messages.c new file mode 100644 index 00000000..770f4a4e --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-ignore-messages.c @@ -0,0 +1,133 @@ +/* + fe-ignore-messages.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "levels.h" +#include "ignore.h" + +static void sig_message_public(SERVER_REC *server, const char *msg, + const char *nick, const char *address, + const char *target) +{ + if (ignore_check(server, nick, address, target, msg, MSGLEVEL_PUBLIC)) + signal_stop(); +} + +static void sig_message_private(SERVER_REC *server, const char *msg, + const char *nick, const char *address) +{ + if (ignore_check(server, nick, address, NULL, msg, MSGLEVEL_MSGS)) + signal_stop(); +} + +static void sig_message_join(SERVER_REC *server, const char *channel, + const char *nick, const char *address) +{ + if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_JOINS)) + signal_stop(); +} + +static void sig_message_part(SERVER_REC *server, const char *channel, + const char *nick, const char *address, + const char *reason) +{ + if (ignore_check(server, nick, address, channel, NULL, MSGLEVEL_PARTS)) + signal_stop(); +} + +static void sig_message_quit(SERVER_REC *server, const char *nick, + const char *address, const char *reason) +{ + if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS)) + signal_stop(); +} + +static void sig_message_kick(SERVER_REC *server, const char *channel, + const char *nick, const char *kicker, + const char *address, const char *reason) +{ + if (ignore_check(server, kicker, address, + channel, reason, MSGLEVEL_KICKS)) + signal_stop(); +} + +static void sig_message_nick(SERVER_REC *server, const char *newnick, + const char *oldnick, const char *address) +{ + if (ignore_check(server, oldnick, address, + NULL, NULL, MSGLEVEL_NICKS) || + ignore_check(server, newnick, address, + NULL, NULL, MSGLEVEL_NICKS)) + signal_stop(); +} + +static void sig_message_own_nick(SERVER_REC *server, const char *newnick, + const char *oldnick, const char *address) +{ + if (ignore_check(server, oldnick, address, NULL, NULL, MSGLEVEL_NICKS)) + signal_stop(); +} + +static void sig_message_invite(SERVER_REC *server, const char *channel, + const char *nick, const char *address) +{ + if (*channel == '\0' || + ignore_check(server, nick, address, + channel, NULL, MSGLEVEL_INVITES)) + signal_stop(); +} + +static void sig_message_topic(SERVER_REC *server, const char *channel, + const char *topic, + const char *nick, const char *address) +{ + if (ignore_check(server, nick, address, + channel, topic, MSGLEVEL_TOPICS)) + signal_stop(); +} + +void fe_ignore_messages_init(void) +{ + signal_add_first("message public", (SIGNAL_FUNC) sig_message_public); + signal_add_first("message private", (SIGNAL_FUNC) sig_message_private); + signal_add_first("message join", (SIGNAL_FUNC) sig_message_join); + signal_add_first("message part", (SIGNAL_FUNC) sig_message_part); + signal_add_first("message quit", (SIGNAL_FUNC) sig_message_quit); + signal_add_first("message kick", (SIGNAL_FUNC) sig_message_kick); + signal_add_first("message nick", (SIGNAL_FUNC) sig_message_nick); + signal_add_first("message own_nick", (SIGNAL_FUNC) sig_message_own_nick); + signal_add_first("message invite", (SIGNAL_FUNC) sig_message_invite); + signal_add_first("message topic", (SIGNAL_FUNC) sig_message_topic); +} + +void fe_ignore_messages_deinit(void) +{ + signal_remove("message public", (SIGNAL_FUNC) sig_message_public); + signal_remove("message private", (SIGNAL_FUNC) sig_message_private); + signal_remove("message join", (SIGNAL_FUNC) sig_message_join); + signal_remove("message part", (SIGNAL_FUNC) sig_message_part); + signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit); + signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick); + signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick); + signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick); + signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite); + signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic); +} diff --git a/apps/irssi/src/fe-common/core/fe-ignore.c b/apps/irssi/src/fe-common/core/fe-ignore.c new file mode 100644 index 00000000..3523d527 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-ignore.c @@ -0,0 +1,235 @@ +/* + fe-ignore.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" + +#include "servers.h" +#include "ignore.h" +#include "printtext.h" + +static char *ignore_get_key(IGNORE_REC *rec) +{ + char *chans, *ret; + + if (rec->channels == NULL) + return g_strdup(rec->mask != NULL ? rec->mask : "*" ); + + chans = g_strjoinv(",", rec->channels); + if (rec->mask == NULL) return chans; + + ret = g_strdup_printf("%s %s", rec->mask, chans); + g_free(chans); + return ret; +} + +static void ignore_print(int index, IGNORE_REC *rec) +{ + GString *options; + char *key, *levels; + + key = ignore_get_key(rec); + levels = bits2level(rec->level); + + options = g_string_new(NULL); + if (rec->exception) g_string_sprintfa(options, "-except "); + if (rec->regexp) g_string_sprintfa(options, "-regexp "); + if (rec->fullword) g_string_sprintfa(options, "-full "); + if (rec->replies) g_string_sprintfa(options, "-replies "); + if (options->len > 1) g_string_truncate(options, options->len-1); + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, + TXT_IGNORE_LINE, index, + key != NULL ? key : "", + levels != NULL ? levels : "", options->str); + g_string_free(options, TRUE); + g_free(key); + g_free(levels); +} + +static void cmd_ignore_show(void) +{ + GSList *tmp; + int index; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_IGNORE_HEADER); + index = 1; + for (tmp = ignores; tmp != NULL; tmp = tmp->next, index++) { + IGNORE_REC *rec = tmp->data; + + ignore_print(index, rec); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_IGNORE_FOOTER); +} + +/* SYNTAX: IGNORE [-regexp | -full] [-pattern ] [-except] [-replies] + [-channels ] [-time ] [] + IGNORE [-regexp | -full] [-pattern ] [-except] [-replies] + [-time ] [] */ +static void cmd_ignore(const char *data) +{ + GHashTable *optlist; + IGNORE_REC *rec; + char *patternarg, *chanarg, *mask, *levels, *timestr; + char **channels; + void *free_arg; + int new_ignore; + + if (*data == '\0') { + cmd_ignore_show(); + return; + } + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, + "ignore", &optlist, &mask, &levels)) + return; + + patternarg = g_hash_table_lookup(optlist, "pattern"); + chanarg = g_hash_table_lookup(optlist, "channels"); + + if (*mask == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + if (*levels == '\0') levels = "ALL"; + + if (active_win->active_server != NULL && + active_win->active_server->ischannel(mask)) { + chanarg = mask; + mask = NULL; + } + channels = (chanarg == NULL || *chanarg == '\0') ? NULL : + g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1); + + rec = ignore_find(NULL, mask, channels); + new_ignore = rec == NULL; + + if (rec == NULL) { + rec = g_new0(IGNORE_REC, 1); + + rec->mask = mask == NULL || *mask == '\0' || + strcmp(mask, "*") == 0 ? NULL : g_strdup(mask); + rec->channels = channels; + } else { + g_free_and_null(rec->pattern); + g_strfreev(channels); + } + + rec->level = combine_level(rec->level, levels); + rec->pattern = (patternarg == NULL || *patternarg == '\0') ? + NULL : g_strdup(patternarg); + rec->exception = g_hash_table_lookup(optlist, "except") != NULL; + rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL; + rec->fullword = g_hash_table_lookup(optlist, "full") != NULL; + rec->replies = g_hash_table_lookup(optlist, "replies") != NULL; + timestr = g_hash_table_lookup(optlist, "time"); + if (timestr != NULL) + rec->unignore_time = time(NULL)+atoi(timestr); + + if (rec->level == 0) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_UNIGNORED, + rec->mask == NULL ? "*" : rec->mask); + } + + if (new_ignore) + ignore_add_rec(rec); + else + ignore_update_rec(rec); + + cmd_params_free(free_arg); +} + +/* SYNTAX: UNIGNORE | */ +static void cmd_unignore(const char *data) +{ + IGNORE_REC *rec; + GSList *tmp; + + if (*data == '\0') + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (is_numeric(data, ' ')) { + /* with index number */ + tmp = g_slist_nth(ignores, atoi(data)-1); + rec = tmp == NULL ? NULL : tmp->data; + } else { + /* with mask */ + const char *chans[2] = { "*", NULL }; + + if (active_win->active_server != NULL && + active_win->active_server->ischannel(data)) { + chans[0] = data; + data = NULL; + } + rec = ignore_find("*", data, (char **) chans); + } + + if (rec != NULL) { + rec->level = 0; + ignore_update_rec(rec); + } else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_IGNORE_NOT_FOUND, data); + } +} + +static void sig_ignore_created(IGNORE_REC *rec) +{ + char *key, *levels; + + key = ignore_get_key(rec); + levels = bits2level(rec->level); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_IGNORED, key, levels); + g_free(key); + g_free(levels); +} + +static void sig_ignore_destroyed(IGNORE_REC *rec) +{ + char *key; + + key = ignore_get_key(rec); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_UNIGNORED, key); + g_free(key); +} + +void fe_ignore_init(void) +{ + command_bind("ignore", NULL, (SIGNAL_FUNC) cmd_ignore); + command_bind("unignore", NULL, (SIGNAL_FUNC) cmd_unignore); + + signal_add("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed); + signal_add("ignore created", (SIGNAL_FUNC) sig_ignore_created); + signal_add("ignore changed", (SIGNAL_FUNC) sig_ignore_created); + + command_set_options("ignore", "regexp full except replies -time -pattern -channels"); +} + +void fe_ignore_deinit(void) +{ + command_unbind("ignore", (SIGNAL_FUNC) cmd_ignore); + command_unbind("unignore", (SIGNAL_FUNC) cmd_unignore); + + signal_remove("ignore destroyed", (SIGNAL_FUNC) sig_ignore_destroyed); + signal_remove("ignore created", (SIGNAL_FUNC) sig_ignore_created); + signal_remove("ignore changed", (SIGNAL_FUNC) sig_ignore_created); +} diff --git a/apps/irssi/src/fe-common/core/fe-log.c b/apps/irssi/src/fe-common/core/fe-log.c new file mode 100644 index 00000000..60d96327 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-log.c @@ -0,0 +1,669 @@ +/* + fe-log.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "servers.h" +#include "levels.h" +#include "misc.h" +#include "log.h" +#include "special-vars.h" +#include "settings.h" + +#include "fe-windows.h" +#include "window-items.h" +#include "formats.h" +#include "themes.h" +#include "printtext.h" + +/* close autologs after 5 minutes of inactivity */ +#define AUTOLOG_INACTIVITY_CLOSE (60*5) + +#define LOG_DIR_CREATE_MODE 0770 + +static int autolog_level; +static int autoremove_tag; +static const char *autolog_path; + +static THEME_REC *log_theme; +static int skip_next_printtext; +static const char *log_theme_name; + +static void log_add_targets(LOG_REC *log, const char *targets, const char *tag) +{ + char **tmp, **items; + + g_return_if_fail(log != NULL); + g_return_if_fail(targets != NULL); + + items = g_strsplit(targets, " ", -1); + + for (tmp = items; *tmp != NULL; tmp++) + log_item_add(log, LOG_ITEM_TARGET, *tmp, tag); + + g_strfreev(items); +} + +/* SYNTAX: LOG OPEN [-noopen] [-autoopen] [-window] [-] + [-targets ] [] */ +static void cmd_log_open(const char *data) +{ + SERVER_REC *server; + GHashTable *optlist; + char *targetarg, *fname, *levels, *servertag; + void *free_arg; + char window[MAX_INT_STRLEN]; + LOG_REC *log; + int level; + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_OPTIONS, + "log open", &optlist, &fname, &levels)) + return; + if (*fname == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + level = level2bits(levels); + log = log_create_rec(fname, level != 0 ? level : MSGLEVEL_ALL); + + /* - */ + server = cmd_options_get_server("log open", optlist, NULL); + servertag = server == NULL ? NULL : server->tag; + + if (g_hash_table_lookup(optlist, "window")) { + /* log by window ref# */ + ltoa(window, active_win->refnum); + log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, + servertag); + } else { + targetarg = g_hash_table_lookup(optlist, "targets"); + if (targetarg != NULL && *targetarg != '\0') + log_add_targets(log, targetarg, servertag); + } + + if (g_hash_table_lookup(optlist, "autoopen")) + log->autoopen = TRUE; + + log_update(log); + + if (log->handle == -1 && g_hash_table_lookup(optlist, "noopen") == NULL) { + /* start logging */ + if (log_start_logging(log)) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_LOG_OPENED, fname); + } else { + log_close(log); + } + } + + cmd_params_free(free_arg); +} + +static LOG_REC *log_find_from_data(const char *data) +{ + GSList *tmp; + + if (!is_numeric(data, ' ')) + return log_find(data); + + /* with index number */ + tmp = g_slist_nth(logs, atoi(data)-1); + return tmp == NULL ? NULL : tmp->data; +} + +/* SYNTAX: LOG CLOSE | */ +static void cmd_log_close(const char *data) +{ + LOG_REC *log; + + log = log_find_from_data(data); + if (log == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data); + else { + log_close(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data); + } +} + +/* SYNTAX: LOG START | */ +static void cmd_log_start(const char *data) +{ + LOG_REC *log; + + log = log_find_from_data(data); + if (log != NULL) { + log_start_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, data); + } +} + +/* SYNTAX: LOG STOP | */ +static void cmd_log_stop(const char *data) +{ + LOG_REC *log; + + log = log_find_from_data(data); + if (log == NULL || log->handle == -1) + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_LOG_NOT_OPEN, data); + else { + log_stop_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, data); + } +} + +static char *log_items_get_list(LOG_REC *log) +{ + GSList *tmp; + GString *str; + char *ret; + + g_return_val_if_fail(log != NULL, NULL); + g_return_val_if_fail(log->items != NULL, NULL); + + str = g_string_new(NULL); + for (tmp = log->items; tmp != NULL; tmp = tmp->next) { + LOG_ITEM_REC *rec = tmp->data; + + g_string_sprintfa(str, "%s, ", rec->name); + } + g_string_truncate(str, str->len-2); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +static void cmd_log_list(void) +{ + GSList *tmp; + char *levelstr, *items; + int index; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_HEADER); + for (tmp = logs, index = 1; tmp != NULL; tmp = tmp->next, index++) { + LOG_REC *rec = tmp->data; + + levelstr = bits2level(rec->level); + items = rec->items == NULL ? NULL : + log_items_get_list(rec); + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST, + index, rec->fname, items != NULL ? items : "", + levelstr, rec->autoopen ? " -autoopen" : ""); + + g_free_not_null(items); + g_free(levelstr); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_LOG_LIST_FOOTER); +} + +static void cmd_log(const char *data, SERVER_REC *server, void *item) +{ + if (*data == '\0') + cmd_log_list(); + else + command_runsub("log", data, server, item); +} + +static LOG_REC *logs_find_item(int type, const char *item, + const char *servertag, LOG_ITEM_REC **ret_item) +{ + LOG_ITEM_REC *logitem; + GSList *tmp; + + for (tmp = logs; tmp != NULL; tmp = tmp->next) { + LOG_REC *log = tmp->data; + + logitem = log_item_find(log, type, item, servertag); + if (logitem != NULL) { + if (ret_item != NULL) *ret_item = logitem; + return log; + } + } + + return NULL; +} + +/* SYNTAX: WINDOW LOG on|off|toggle [] */ +static void cmd_window_log(const char *data) +{ + LOG_REC *log; + char *set, *fname, window[MAX_INT_STRLEN]; + void *free_arg; + int open_log, close_log; + + if (!cmd_get_params(data, &free_arg, 2, &set, &fname)) + return; + + ltoa(window, active_win->refnum); + log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL); + + open_log = close_log = FALSE; + if (g_strcasecmp(set, "ON") == 0) + open_log = TRUE; + else if (g_strcasecmp(set, "OFF") == 0) { + close_log = TRUE; + } else if (g_strcasecmp(set, "TOGGLE") == 0) { + open_log = log == NULL; + close_log = log != NULL; + } else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_NOT_TOGGLE); + cmd_params_free(free_arg); + return; + } + + if (open_log && log == NULL) { + /* irc.log. or irc.log.Window */ + fname = *fname != '\0' ? g_strdup(fname) : + g_strdup_printf("~/irc.log.%s%s", + active_win->name != NULL ? active_win->name : "Window", + active_win->name != NULL ? "" : window); + log = log_create_rec(fname, MSGLEVEL_ALL); + log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL); + log_update(log); + g_free(fname); + } + + if (open_log && log != NULL) { + log_start_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_OPENED, log->fname); + } else if (close_log && log != NULL && log->handle != -1) { + log_stop_logging(log); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_CLOSED, log->fname); + } + + cmd_params_free(free_arg); +} + +/* Create log file entry to window, but don't start logging */ +/* SYNTAX: WINDOW LOGFILE */ +static void cmd_window_logfile(const char *data) +{ + LOG_REC *log; + char window[MAX_INT_STRLEN]; + + ltoa(window, active_win->refnum); + log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, window, NULL, NULL); + + if (log != NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE_LOGGING); + return; + } + + log = log_create_rec(data, MSGLEVEL_ALL); + log_item_add(log, LOG_ITEM_WINDOW_REFNUM, window, NULL); + log_update(log); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WINDOWLOG_FILE, data); +} + +/* window's refnum changed - update the logs to log the new window refnum */ +static void sig_window_refnum_changed(WINDOW_REC *window, gpointer old_refnum) +{ + char winnum[MAX_INT_STRLEN]; + LOG_REC *log; + LOG_ITEM_REC *item; + + ltoa(winnum, GPOINTER_TO_INT(old_refnum)); + log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, winnum, NULL, &item); + + if (log != NULL) { + ltoa(winnum, window->refnum); + + g_free(item->name); + item->name = g_strdup(winnum); + } +} + +static void sig_server_disconnected(SERVER_REC *server) +{ + LOG_ITEM_REC *logitem; + GSList *tmp, *next; + + for (tmp = logs; tmp != NULL; tmp = next) { + LOG_REC *log = tmp->data; + next = tmp->next; + + if (!log->temp || log->items == NULL) + continue; + + logitem = log->items->data; + if (logitem->type == LOG_ITEM_TARGET && + g_strcasecmp(logitem->servertag, server->tag) == 0) + log_close(log); + } +} + +static void autologs_close_all(void) +{ + GSList *tmp, *next; + + for (tmp = logs; tmp != NULL; tmp = next) { + LOG_REC *rec = tmp->data; + + next = tmp->next; + if (rec->temp) log_close(rec); + } +} + +static void autolog_open(SERVER_REC *server, const char *target) +{ + LOG_REC *log; + char *fname, *dir, *fixed_target, *tag; + + tag = server == NULL ? NULL : server->tag; + log = logs_find_item(LOG_ITEM_TARGET, target, tag, NULL); + if (log != NULL && !log->failed) { + log_start_logging(log); + return; + } + + /* '/' -> '_' - don't even accidentally try to log to + #../../../file if you happen to join to such channel.. */ + fixed_target = g_strdup(target); + replace_chars(fixed_target, '/', '_'); + fname = parse_special_string(autolog_path, server, NULL, + fixed_target, NULL, 0); + g_free(fixed_target); + + if (log_find(fname) == NULL) { + log = log_create_rec(fname, autolog_level); + log_item_add(log, LOG_ITEM_TARGET, target, tag); + + dir = g_dirname(log->real_fname); + mkpath(dir, LOG_DIR_CREATE_MODE); + g_free(dir); + + log->temp = TRUE; + log_update(log); + log_start_logging(log); + } + g_free(fname); +} + +static void autolog_open_check(SERVER_REC *server, const char *target, + int level) +{ + char **targets, **tmp; + + if (level == MSGLEVEL_PARTS || /* FIXME: kind of a kludge, but we don't want to reopen logs when we're parting the channel with /WINDOW CLOSE.. */ + (autolog_level & level) == 0 || target == NULL || *target == '\0') + return; + + /* there can be multiple targets separated with comma */ + targets = g_strsplit(target, ",", -1); + for (tmp = targets; *tmp != NULL; tmp++) + autolog_open(server, *tmp); + g_strfreev(targets); +} + +static void log_single_line(WINDOW_REC *window, void *server, + const char *target, int level, const char *text) +{ + char windownum[MAX_INT_STRLEN]; + char **targets, **tmp; + LOG_REC *log; + + /* save to log created with /WINDOW LOG */ + ltoa(windownum, window->refnum); + log = logs_find_item(LOG_ITEM_WINDOW_REFNUM, + windownum, NULL, NULL); + if (log != NULL) log_write_rec(log, text, level); + + if (target == NULL) + log_file_write(server, NULL, level, text, FALSE); + else { + /* there can be multiple items separated with comma */ + targets = g_strsplit(target, ",", -1); + for (tmp = targets; *tmp != NULL; tmp++) + log_file_write(server, *tmp, level, text, FALSE); + g_strfreev(targets); + } +} + +static void log_line(WINDOW_REC *window, SERVER_REC *server, + const char *target, int level, const char *text) +{ + char **lines, **tmp; + + if (level == MSGLEVEL_NEVER) + return; + + /* let autolog open the log records */ + autolog_open_check(server, target, level); + + if (logs == NULL) + return; + + /* text may contain one or more lines, log wants to eat them one + line at a time */ + lines = g_strsplit(text, "\n", -1); + for (tmp = lines; *tmp != NULL; tmp++) + log_single_line(window, server, target, level, *tmp); + g_strfreev(lines); +} + +static void sig_printtext_stripped(TEXT_DEST_REC *dest, const char *text) +{ + if (skip_next_printtext) { + skip_next_printtext = FALSE; + return; + } + + log_line(dest->window, dest->server, dest->target, + dest->level, text); +} + +static void sig_print_format(THEME_REC *theme, const char *module, + TEXT_DEST_REC *dest, void *formatnum, char **args) +{ + char *str, *stripped, *linestart, *tmp; + + if (log_theme == NULL) { + /* theme isn't loaded for some reason (/reload destroys it), + reload it. */ + log_theme = theme_load(log_theme_name); + if (log_theme == NULL) return; + } + + if (theme == log_theme) + return; + + str = format_get_text_theme_charargs(log_theme, module, dest, + GPOINTER_TO_INT(formatnum), args); + skip_next_printtext = TRUE; + + if (*str != '\0') { + /* add the line start format */ + linestart = format_get_level_tag(log_theme, dest); + tmp = str; + str = format_add_linestart(tmp, linestart); + g_free_not_null(linestart); + g_free(tmp); + + /* strip colors from text, log it. */ + stripped = strip_codes(str); + log_line(dest->window, dest->server, dest->target, + dest->level, stripped); + g_free(stripped); + } + g_free(str); + +} + +static int sig_autoremove(void) +{ + SERVER_REC *server; + LOG_ITEM_REC *logitem; + GSList *tmp, *next; + time_t removetime; + + removetime = time(NULL)-AUTOLOG_INACTIVITY_CLOSE; + for (tmp = logs; tmp != NULL; tmp = next) { + LOG_REC *log = tmp->data; + + next = tmp->next; + + if (!log->temp || log->last > removetime || log->items == NULL) + continue; + + /* Close only logs with private messages */ + logitem = log->items->data; + if (logitem->servertag == NULL) + continue; + + server = server_find_tag(logitem->servertag); + if (logitem->type == LOG_ITEM_TARGET && + server != NULL && !server->ischannel(logitem->name)) + log_close(log); + } + return 1; +} + +static void sig_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item) +{ + LOG_REC *log; + + log = logs_find_item(LOG_ITEM_TARGET, item->name, + item->server == NULL ? NULL : + item->server->tag, NULL); + if (log != NULL && log->temp) + log_close(log); +} + +static void sig_log_locked(LOG_REC *log) +{ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_LOG_LOCKED, log->fname); +} + +static void sig_log_create_failed(LOG_REC *log) +{ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_LOG_CREATE_FAILED, log->fname, g_strerror(errno)); +} + +static void sig_awaylog_show(LOG_REC *log, gpointer pmsgs, gpointer pfilepos) +{ + char *str; + int msgs, filepos; + + msgs = GPOINTER_TO_INT(pmsgs); + filepos = GPOINTER_TO_INT(pfilepos); + + if (msgs == 0) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_NO_AWAY_MSGS, log->fname); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOG_AWAY_MSGS, log->fname, msgs); + + str = g_strdup_printf("\"%s\" %d", log->fname, filepos); + signal_emit("command cat", 1, str); + g_free(str); + } +} + +static void sig_theme_destroyed(THEME_REC *theme) +{ + if (theme == log_theme) + log_theme = NULL; +} + +static void read_settings(void) +{ + int old_autolog = autolog_level; + + autolog_path = settings_get_str("autolog_path"); + autolog_level = !settings_get_bool("autolog") ? 0 : + level2bits(settings_get_str("autolog_level")); + + if (old_autolog && !autolog_level) + autologs_close_all(); + + /* write to log files with different theme? */ + if (log_theme_name != NULL) + signal_remove("print format", (SIGNAL_FUNC) sig_print_format); + log_theme_name = settings_get_str("log_theme"); + if (*log_theme_name == '\0') + log_theme_name = NULL; + else + signal_add("print format", (SIGNAL_FUNC) sig_print_format); + + log_theme = log_theme_name == NULL ? NULL : + theme_load(log_theme_name); +} + +void fe_log_init(void) +{ + autoremove_tag = g_timeout_add(60000, (GSourceFunc) sig_autoremove, NULL); + skip_next_printtext = FALSE; + + settings_add_str("log", "autolog_path", "~/irclogs/$tag/$0.log"); + settings_add_str("log", "autolog_level", "all -crap -clientcrap -ctcps"); + settings_add_bool("log", "autolog", FALSE); + settings_add_str("log", "log_theme", ""); + + autolog_level = 0; + log_theme_name = NULL; + read_settings(); + + command_bind("log", NULL, (SIGNAL_FUNC) cmd_log); + command_bind("log open", NULL, (SIGNAL_FUNC) cmd_log_open); + command_bind("log close", NULL, (SIGNAL_FUNC) cmd_log_close); + command_bind("log start", NULL, (SIGNAL_FUNC) cmd_log_start); + command_bind("log stop", NULL, (SIGNAL_FUNC) cmd_log_stop); + command_bind("window log", NULL, (SIGNAL_FUNC) cmd_window_log); + command_bind("window logfile", NULL, (SIGNAL_FUNC) cmd_window_logfile); + signal_add_first("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); + signal_add("window item destroy", (SIGNAL_FUNC) sig_window_item_destroy); + signal_add("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("log locked", (SIGNAL_FUNC) sig_log_locked); + signal_add("log create failed", (SIGNAL_FUNC) sig_log_create_failed); + signal_add("awaylog show", (SIGNAL_FUNC) sig_awaylog_show); + signal_add("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + command_set_options("log open", "noopen autoopen -targets window"); +} + +void fe_log_deinit(void) +{ + g_source_remove(autoremove_tag); + if (log_theme_name != NULL) + signal_remove("print format", (SIGNAL_FUNC) sig_print_format); + + command_unbind("log", (SIGNAL_FUNC) cmd_log); + command_unbind("log open", (SIGNAL_FUNC) cmd_log_open); + command_unbind("log close", (SIGNAL_FUNC) cmd_log_close); + command_unbind("log start", (SIGNAL_FUNC) cmd_log_start); + command_unbind("log stop", (SIGNAL_FUNC) cmd_log_stop); + command_unbind("window log", (SIGNAL_FUNC) cmd_window_log); + command_unbind("window logfile", (SIGNAL_FUNC) cmd_window_logfile); + signal_remove("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped); + signal_remove("window item destroy", (SIGNAL_FUNC) sig_window_item_destroy); + signal_remove("window refnum changed", (SIGNAL_FUNC) sig_window_refnum_changed); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("log locked", (SIGNAL_FUNC) sig_log_locked); + signal_remove("log create failed", (SIGNAL_FUNC) sig_log_create_failed); + signal_remove("awaylog show", (SIGNAL_FUNC) sig_awaylog_show); + signal_remove("theme destroyed", (SIGNAL_FUNC) sig_theme_destroyed); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/fe-common/core/fe-messages.c b/apps/irssi/src/fe-common/core/fe-messages.c new file mode 100644 index 00000000..fa5e88af --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-messages.c @@ -0,0 +1,684 @@ +/* + fe-messages.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "special-vars.h" +#include "settings.h" + +#include "window-items.h" +#include "fe-queries.h" +#include "channels.h" +#include "nicklist.h" +#include "hilight-text.h" +#include "ignore.h" +#include "printtext.h" + +#define ishighalnum(c) ((unsigned char) (c) >= 128 || isalnum(c)) + +static GHashTable *printnicks; + +/* convert _underlined_ and *bold* words (and phrases) to use real + underlining or bolding */ +char *expand_emphasis(WI_ITEM_REC *item, const char *text) +{ + GString *str; + char *ret; + int pos; + + g_return_val_if_fail(text != NULL, NULL); + + str = g_string_new(text); + + for (pos = 0; pos < str->len; pos++) { + char type, *bgn, *end; + + bgn = str->str + pos; + + if (*bgn == '*') + type = 2; /* bold */ + else if (*bgn == '_') + type = 31; /* underlined */ + else + continue; + + /* check that the beginning marker starts a word, and + that the matching end marker ends a word */ + if ((pos > 0 && !isspace(bgn[-1])) || !ishighalnum(bgn[1])) + continue; + if ((end = strchr(bgn+1, *bgn)) == NULL) + continue; + if (!ishighalnum(end[-1]) || ishighalnum(end[1]) || + end[1] == type || end[1] == '*' || end[1] == '_') + continue; + + if (IS_CHANNEL(item)) { + /* check that this isn't a _nick_, we don't want to + use emphasis on them. */ + int found; + char c; + + c = end[1]; + end[1] = '\0'; + found = nicklist_find(CHANNEL(item), bgn) != NULL; + end[1] = c; + if (found) continue; + } + + /* allow only *word* emphasis, not *multiple words* */ + if (!settings_get_bool("emphasis_multiword")) { + char *c; + for (c = bgn+1; c != end; c++) { + if (!ishighalnum(*c)) + break; + } + if (c != end) continue; + } + + if (settings_get_bool("emphasis_replace")) { + *bgn = *end = type; + pos += (end-bgn); + } else { + g_string_insert_c(str, pos, type); + pos += (end - bgn) + 2; + g_string_insert_c(str, pos++, type); + } + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick) +{ + NICK_REC *nickrec; + char *emptystr; + + g_return_val_if_fail(nick != NULL, NULL); + + if (!settings_get_bool("show_nickmode")) + return ""; + + emptystr = settings_get_bool("show_nickmode_empty") ? " " : ""; + + nickrec = channel == NULL ? NULL : + nicklist_find(channel, nick); + return nickrec == NULL ? emptystr : + (nickrec->op ? "@" : (nickrec->voice ? "+" : emptystr)); +} + +static char *channel_get_nickmode_rec(NICK_REC *nickrec) +{ + char *emptystr; + + if (!settings_get_bool("show_nickmode")) + return ""; + + emptystr = settings_get_bool("show_nickmode_empty") ? " " : ""; + + return nickrec == NULL ? emptystr : + (nickrec->op ? "@" : (nickrec->voice ? "+" : emptystr)); +} + +static void sig_message_public(SERVER_REC *server, const char *msg, + const char *nick, const char *address, + const char *target, NICK_REC *nickrec) +{ + CHANNEL_REC *chanrec; + const char *nickmode, *printnick; + int for_me, print_channel, level; + char *color, *freemsg = NULL; + + /* NOTE: this may return NULL if some channel is just closed with + /WINDOW CLOSE and server still sends the few last messages */ + chanrec = channel_find(server, target); + if (nickrec == NULL && chanrec != NULL) + nickrec = nicklist_find(chanrec, nick); + + for_me = nick_match_msg(chanrec, msg, server->nick); + color = for_me ? NULL : + hilight_match_nick(server, target, nick, address, MSGLEVEL_PUBLIC, msg); + + print_channel = chanrec == NULL || + !window_item_is_active((WI_ITEM_REC *) chanrec); + if (!print_channel && settings_get_bool("print_active_channel") && + window_item_window((WI_ITEM_REC *) chanrec)->items->next != NULL) + print_channel = TRUE; + + level = MSGLEVEL_PUBLIC | (for_me || color != NULL ? + MSGLEVEL_HILIGHT : MSGLEVEL_NOHILIGHT); + + if (settings_get_bool("emphasis")) + msg = freemsg = expand_emphasis((WI_ITEM_REC *) chanrec, msg); + + /* get nick mode & nick what to print the msg with + (in case there's multiple identical nicks) */ + nickmode = channel_get_nickmode_rec(nickrec); + printnick = nickrec == NULL ? nick : + g_hash_table_lookup(printnicks, nickrec); + if (printnick == NULL) + printnick = nick; + + if (!print_channel) { + /* message to active channel in window */ + if (color != NULL) { + /* highlighted nick */ + printformat(server, target, level, + TXT_PUBMSG_HILIGHT, + color, printnick, msg, nickmode); + } else { + printformat(server, target, level, + for_me ? TXT_PUBMSG_ME : TXT_PUBMSG, + printnick, msg, nickmode); + } + } else { + /* message to not existing/active channel */ + if (color != NULL) { + /* highlighted nick */ + printformat(server, target, level, + TXT_PUBMSG_HILIGHT_CHANNEL, + color, printnick, target, msg, nickmode); + } else { + printformat(server, target, level, + for_me ? TXT_PUBMSG_ME_CHANNEL : + TXT_PUBMSG_CHANNEL, + printnick, target, msg, nickmode); + } + } + + g_free_not_null(freemsg); + g_free_not_null(color); +} + +static void sig_message_private(SERVER_REC *server, const char *msg, + const char *nick, const char *address) +{ + QUERY_REC *query; + char *freemsg = NULL; + + query = query_find(server, nick); + + if (settings_get_bool("emphasis")) + msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg); + + printformat(server, nick, MSGLEVEL_MSGS, + query == NULL ? TXT_MSG_PRIVATE : + TXT_MSG_PRIVATE_QUERY, nick, address, msg); + + g_free_not_null(freemsg); +} + +static void print_own_channel_message(SERVER_REC *server, CHANNEL_REC *channel, + const char *target, const char *msg) +{ + WINDOW_REC *window; + const char *nickmode; + char *freemsg = NULL; + int print_channel; + + nickmode = channel_get_nickmode(channel, server->nick); + + window = channel == NULL ? NULL : + window_item_window((WI_ITEM_REC *) channel); + + print_channel = window == NULL || + window->active != (WI_ITEM_REC *) channel; + + if (!print_channel && settings_get_bool("print_active_channel") && + window != NULL && g_slist_length(window->items) > 1) + print_channel = TRUE; + + if (settings_get_bool("emphasis")) + msg = freemsg = expand_emphasis((WI_ITEM_REC *) channel, msg); + + if (!print_channel) { + printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT, + TXT_OWN_MSG, server->nick, msg, nickmode); + } else { + printformat(server, target, MSGLEVEL_PUBLIC | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT, + TXT_OWN_MSG_CHANNEL, server->nick, target, msg, nickmode); + } + + g_free_not_null(freemsg); +} + +static void sig_message_own_public(SERVER_REC *server, const char *msg, + const char *target) +{ + CHANNEL_REC *channel; + + g_return_if_fail(server != NULL); + g_return_if_fail(msg != NULL); + + channel = channel_find(server, target); + print_own_channel_message(server, channel, target, msg); +} + +static void sig_message_own_private(SERVER_REC *server, const char *msg, + const char *target, const char *origtarget) +{ + QUERY_REC *query; + char *freemsg = NULL; + + g_return_if_fail(server != NULL); + g_return_if_fail(msg != NULL); + + if (target == NULL) { + /* this should only happen if some special target failed and + we should display some error message. currently the special + targets are only ',' and '.'. */ + g_return_if_fail(strcmp(origtarget, ",") == 0 || + strcmp(origtarget, ".") == 0); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + *origtarget == ',' ? TXT_NO_MSGS_GOT : + TXT_NO_MSGS_SENT); + signal_stop(); + return; + } + + query = privmsg_get_query(server, target, TRUE, MSGLEVEL_MSGS); + + if (settings_get_bool("emphasis")) + msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg); + + printformat(server, target, + MSGLEVEL_MSGS | MSGLEVEL_NOHILIGHT | MSGLEVEL_NO_ACT, + query == NULL ? TXT_OWN_MSG_PRIVATE : + TXT_OWN_MSG_PRIVATE_QUERY, target, msg, server->nick); + + g_free_not_null(freemsg); +} + +static void sig_message_join(SERVER_REC *server, const char *channel, + const char *nick, const char *address) +{ + printformat(server, channel, MSGLEVEL_JOINS, + TXT_JOIN, nick, address, channel); +} + +static void sig_message_part(SERVER_REC *server, const char *channel, + const char *nick, const char *address, + const char *reason) +{ + printformat(server, channel, MSGLEVEL_PARTS, + TXT_PART, nick, address, channel, reason); +} + +static void sig_message_quit(SERVER_REC *server, const char *nick, + const char *address, const char *reason) +{ + WINDOW_REC *window; + GString *chans; + GSList *tmp, *windows; + char *print_channel; + int once, count; + + if (ignore_check(server, nick, address, NULL, reason, MSGLEVEL_QUITS)) + return; + + print_channel = NULL; + once = settings_get_bool("show_quit_once"); + + count = 0; windows = NULL; + chans = g_string_new(NULL); + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *rec = tmp->data; + + if (!nicklist_find(rec, nick)) + continue; + + if (ignore_check(server, nick, address, rec->name, + reason, MSGLEVEL_QUITS)) { + count++; + continue; + } + + if (print_channel == NULL || + active_win->active == (WI_ITEM_REC *) rec) + print_channel = rec->name; + + if (once) + g_string_sprintfa(chans, "%s,", rec->name); + else { + window = window_item_window((WI_ITEM_REC *) rec); + if (g_slist_find(windows, window) == NULL) { + windows = g_slist_append(windows, window); + printformat(server, rec->name, MSGLEVEL_QUITS, + TXT_QUIT, nick, address, reason, + rec->name); + } + } + count++; + } + g_slist_free(windows); + + if (!once) { + /* check if you had query with the nick and + display the quit there too */ + QUERY_REC *query = query_find(server, nick); + if (query != NULL) { + printformat(server, nick, MSGLEVEL_QUITS, + TXT_QUIT, nick, address, reason, ""); + } + } + + if (once || count == 0) { + if (chans->len > 0) + g_string_truncate(chans, chans->len-1); + printformat(server, print_channel, MSGLEVEL_QUITS, + count <= 1 ? TXT_QUIT : TXT_QUIT_ONCE, + nick, address, reason, chans->str); + } + g_string_free(chans, TRUE); +} + +static void sig_message_kick(SERVER_REC *server, const char *channel, + const char *nick, const char *kicker, + const char *address, const char *reason) +{ + printformat(server, channel, MSGLEVEL_KICKS, + TXT_KICK, nick, channel, kicker, reason); +} + +static void print_nick_change_channel(SERVER_REC *server, const char *channel, + const char *newnick, const char *oldnick, + const char *address, + int ownnick) +{ + if (ignore_check(server, oldnick, address, + channel, newnick, MSGLEVEL_NICKS)) + return; + + printformat(server, channel, MSGLEVEL_NICKS, + ownnick ? TXT_YOUR_NICK_CHANGED : TXT_NICK_CHANGED, + oldnick, newnick, channel); +} + +static void print_nick_change(SERVER_REC *server, const char *newnick, + const char *oldnick, const char *address, + int ownnick) +{ + GSList *tmp, *windows; + int msgprint; + + msgprint = FALSE; + + /* Print to each channel/query where the nick is. + Don't print more than once to the same window. */ + windows = NULL; + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + WINDOW_REC *window = + window_item_window((WI_ITEM_REC *) channel); + + if (nicklist_find(channel, newnick) == NULL || + g_slist_find(windows, window) != NULL) + continue; + + windows = g_slist_append(windows, window); + print_nick_change_channel(server, channel->name, newnick, + oldnick, address, ownnick); + msgprint = TRUE; + } + + for (tmp = server->queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *query = tmp->data; + WINDOW_REC *window = + window_item_window((WI_ITEM_REC *) query); + + if (g_strcasecmp(query->name, oldnick) != 0 || + g_slist_find(windows, window) != NULL) + continue; + + windows = g_slist_append(windows, window); + print_nick_change_channel(server, query->name, newnick, + oldnick, address, ownnick); + msgprint = TRUE; + } + g_slist_free(windows); + + if (!msgprint && ownnick) { + printformat(server, NULL, MSGLEVEL_NICKS, + TXT_YOUR_NICK_CHANGED, oldnick, newnick, ""); + } +} + +static void sig_message_nick(SERVER_REC *server, const char *newnick, + const char *oldnick, const char *address) +{ + print_nick_change(server, newnick, oldnick, address, FALSE); +} + +static void sig_message_own_nick(SERVER_REC *server, const char *newnick, + const char *oldnick, const char *address) +{ + print_nick_change(server, newnick, oldnick, address, TRUE); +} + +static void sig_message_invite(SERVER_REC *server, const char *channel, + const char *nick, const char *address) +{ + char *str; + + str = show_lowascii(channel); + printformat(server, NULL, MSGLEVEL_INVITES, + TXT_INVITE, nick, str); + g_free(str); +} + +static void sig_message_topic(SERVER_REC *server, const char *channel, + const char *topic, + const char *nick, const char *address) +{ + printformat(server, channel, MSGLEVEL_TOPICS, + *topic != '\0' ? TXT_NEW_TOPIC : TXT_TOPIC_UNSET, + nick, channel, topic); +} + +static int printnick_exists(NICK_REC *first, NICK_REC *ignore, + const char *nick) +{ + char *printnick; + + while (first != NULL) { + if (first != ignore) { + printnick = g_hash_table_lookup(printnicks, first); + if (printnick != NULL && strcmp(printnick, nick) == 0) + return TRUE; + } + + first = first->next; + } + + return FALSE; +} + +static NICK_REC *printnick_find_original(NICK_REC *nick) +{ + while (nick != NULL) { + if (g_hash_table_lookup(printnicks, nick) == NULL) + return nick; + + nick = nick->next; + } + + return NULL; +} + +static void sig_nicklist_new(CHANNEL_REC *channel, NICK_REC *nick) +{ + NICK_REC *firstnick; + GString *newnick; + char *nickhost, *p; + int n; + + if (nick->host == NULL) + return; + + firstnick = g_hash_table_lookup(channel->nicks, nick->nick); + if (firstnick->next == NULL) + return; + + if (nick == channel->ownnick) { + /* own nick is being added, might be a nick change and + someone else having the original nick already in use.. */ + nick = printnick_find_original(firstnick->next); + if (nick == NULL) + return; /* nope, we have it */ + } + + /* identical nick already exists, have to change it somehow.. */ + p = strchr(nick->host, '@'); + if (p == NULL) p = nick->host; else p++; + + nickhost = g_strdup_printf("%s@%s", nick->nick, p); + p = strchr(nickhost+strlen(nick->nick), '.'); + if (p != NULL) *p = '\0'; + + if (!printnick_exists(firstnick, nick, nickhost)) { + /* use nick@host */ + g_hash_table_insert(printnicks, nick, nickhost); + return; + } + + newnick = g_string_new(NULL); + n = 2; + do { + g_string_sprintf(newnick, "%s%d", nickhost, n); + n++; + } while (printnick_exists(firstnick, nick, newnick->str)); + + g_hash_table_insert(printnicks, nick, newnick->str); + g_string_free(newnick, FALSE); + g_free(nickhost); +} + +static void sig_nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick) +{ + char *nickname; + + nickname = g_hash_table_lookup(printnicks, nick); + if (nickname != NULL) { + g_free(nickname); + g_hash_table_remove(printnicks, nick); + } +} + +static void sig_nicklist_changed(CHANNEL_REC *channel, NICK_REC *nick) +{ + sig_nicklist_remove(channel, nick); + sig_nicklist_new(channel, nick); +} + +static void sig_channel_joined(CHANNEL_REC *channel) +{ + NICK_REC *nick; + char *nickname; + + /* channel->ownnick is set at this point - check if our own nick + has been changed, if it was set it back to the original nick and + change the previous original to something else */ + + nickname = g_hash_table_lookup(printnicks, channel->ownnick); + if (nickname == NULL) + return; + + g_free(nickname); + g_hash_table_remove(printnicks, channel->ownnick); + + /* our own nick is guaranteed to be the first in list */ + nick = channel->ownnick->next; + while (nick != NULL) { + if (g_hash_table_lookup(printnicks, nick) == NULL) { + sig_nicklist_new(channel, nick); + break; + } + nick = nick->next; + } +} + +static void g_hash_free_value(void *key, void *value) +{ + g_free(value); +} + +void fe_messages_init(void) +{ + printnicks = g_hash_table_new((GHashFunc) g_direct_hash, + (GCompareFunc) g_direct_equal); + + settings_add_bool("lookandfeel", "emphasis", TRUE); + settings_add_bool("lookandfeel", "emphasis_replace", FALSE); + settings_add_bool("lookandfeel", "emphasis_multiword", FALSE); + settings_add_bool("lookandfeel", "show_nickmode", TRUE); + settings_add_bool("lookandfeel", "show_nickmode_empty", TRUE); + settings_add_bool("lookandfeel", "print_active_channel", FALSE); + settings_add_bool("lookandfeel", "show_quit_once", FALSE); + + signal_add("message public", (SIGNAL_FUNC) sig_message_public); + signal_add("message private", (SIGNAL_FUNC) sig_message_private); + signal_add("message own_public", (SIGNAL_FUNC) sig_message_own_public); + signal_add("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_add("message join", (SIGNAL_FUNC) sig_message_join); + signal_add("message part", (SIGNAL_FUNC) sig_message_part); + signal_add("message quit", (SIGNAL_FUNC) sig_message_quit); + signal_add("message kick", (SIGNAL_FUNC) sig_message_kick); + signal_add("message nick", (SIGNAL_FUNC) sig_message_nick); + signal_add("message own_nick", (SIGNAL_FUNC) sig_message_own_nick); + signal_add("message invite", (SIGNAL_FUNC) sig_message_invite); + signal_add("message topic", (SIGNAL_FUNC) sig_message_topic); + + signal_add("nicklist new", (SIGNAL_FUNC) sig_nicklist_new); + signal_add("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove); + signal_add("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed); + signal_add("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new); + signal_add("channel joined", (SIGNAL_FUNC) sig_channel_joined); +} + +void fe_messages_deinit(void) +{ + g_hash_table_foreach(printnicks, (GHFunc) g_hash_free_value, NULL); + g_hash_table_destroy(printnicks); + + signal_remove("message public", (SIGNAL_FUNC) sig_message_public); + signal_remove("message private", (SIGNAL_FUNC) sig_message_private); + signal_remove("message own_public", (SIGNAL_FUNC) sig_message_own_public); + signal_remove("message own_private", (SIGNAL_FUNC) sig_message_own_private); + signal_remove("message join", (SIGNAL_FUNC) sig_message_join); + signal_remove("message part", (SIGNAL_FUNC) sig_message_part); + signal_remove("message quit", (SIGNAL_FUNC) sig_message_quit); + signal_remove("message kick", (SIGNAL_FUNC) sig_message_kick); + signal_remove("message nick", (SIGNAL_FUNC) sig_message_nick); + signal_remove("message own_nick", (SIGNAL_FUNC) sig_message_own_nick); + signal_remove("message invite", (SIGNAL_FUNC) sig_message_invite); + signal_remove("message topic", (SIGNAL_FUNC) sig_message_topic); + + signal_remove("nicklist new", (SIGNAL_FUNC) sig_nicklist_new); + signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove); + signal_remove("nicklist changed", (SIGNAL_FUNC) sig_nicklist_changed); + signal_remove("nicklist host changed", (SIGNAL_FUNC) sig_nicklist_new); + signal_remove("channel joined", (SIGNAL_FUNC) sig_channel_joined); +} diff --git a/apps/irssi/src/fe-common/core/fe-messages.h b/apps/irssi/src/fe-common/core/fe-messages.h new file mode 100644 index 00000000..afe7644d --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-messages.h @@ -0,0 +1,10 @@ +#ifndef __FE_MESSAGES_H +#define __FE_MESSAGES_H + +/* convert _underlined_ and *bold* words (and phrases) to use real + underlining or bolding */ +char *expand_emphasis(WI_ITEM_REC *item, const char *text); + +char *channel_get_nickmode(CHANNEL_REC *channel, const char *nick); + +#endif diff --git a/apps/irssi/src/fe-common/core/fe-modules.c b/apps/irssi/src/fe-common/core/fe-modules.c new file mode 100644 index 00000000..e351dc71 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-modules.c @@ -0,0 +1,161 @@ +/* + fe-common-core.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "modules.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "chat-protocols.h" + +#include "printtext.h" + +static void sig_module_error(void *number, const char *module, + const char *data) +{ + switch (GPOINTER_TO_INT(number)) { + case MODULE_ERROR_ALREADY_LOADED: + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_MODULE_ALREADY_LOADED, module); + break; + case MODULE_ERROR_LOAD: + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_MODULE_LOAD_ERROR, module, data); + break; + case MODULE_ERROR_INVALID: + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_MODULE_INVALID, module); + break; + } +} + +static void sig_module_loaded(MODULE_REC *rec) +{ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_MODULE_LOADED, rec->name); +} + +static void sig_module_unloaded(MODULE_REC *rec) +{ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_MODULE_UNLOADED, rec->name); +} + +static void cmd_load_list(void) +{ + GSList *tmp; + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_MODULE_HEADER); + for (tmp = modules; tmp != NULL; tmp = tmp->next) { + MODULE_REC *rec = tmp->data; + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_MODULE_LINE, rec->name); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_MODULE_FOOTER); +} + +static char **module_prefixes_get(void) +{ + GSList *tmp; + char **list, *name; + int count; + + list = g_new(char *, 2 + 2*g_slist_length(chat_protocols)); + list[0] = "fe"; + + count = 1; + for (tmp = chat_protocols; tmp != NULL; tmp = tmp->next) { + CHAT_PROTOCOL_REC *rec = tmp->data; + + name = g_strdup(rec->name); + g_strdown(name); + + list[count++] = name; + list[count++] = g_strconcat("fe_", name, NULL); + } + list[count] = NULL; + + return list; +} + +static void module_prefixes_free(char **list) +{ + char **pos = list+1; + + while (*pos != NULL) { + g_free(*pos); + pos++; + } + g_free(list); +} + +/* SYNTAX: LOAD */ +static void cmd_load(const char *data) +{ +#ifdef HAVE_GMODULE + char **module_prefixes; + + g_return_if_fail(data != NULL); + if (*data == '\0') + cmd_load_list(); + else { + module_prefixes = module_prefixes_get(); + module_load(data, module_prefixes); + module_prefixes_free(module_prefixes); + } +#else + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Dynamic modules loading not supported"); +#endif +} + +/* SYNTAX: UNLOAD */ +static void cmd_unload(const char *data) +{ + MODULE_REC *rec; + + g_return_if_fail(data != NULL); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + rec = module_find(data); + if (rec != NULL) module_unload(rec); +} + +void fe_modules_init(void) +{ + signal_add("module error", (SIGNAL_FUNC) sig_module_error); + signal_add("module loaded", (SIGNAL_FUNC) sig_module_loaded); + signal_add("module unloaded", (SIGNAL_FUNC) sig_module_unloaded); + + command_bind("load", NULL, (SIGNAL_FUNC) cmd_load); + command_bind("unload", NULL, (SIGNAL_FUNC) cmd_unload); +} + +void fe_modules_deinit(void) +{ + signal_remove("module error", (SIGNAL_FUNC) sig_module_error); + signal_remove("module loaded", (SIGNAL_FUNC) sig_module_loaded); + signal_remove("module unloaded", (SIGNAL_FUNC) sig_module_unloaded); + + command_unbind("load", (SIGNAL_FUNC) cmd_load); + command_unbind("unload", (SIGNAL_FUNC) cmd_unload); +} diff --git a/apps/irssi/src/fe-common/core/fe-queries.c b/apps/irssi/src/fe-common/core/fe-queries.c new file mode 100644 index 00000000..1924464a --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-queries.c @@ -0,0 +1,380 @@ +/* + fe-queries.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "queries.h" + +#include "fe-windows.h" +#include "window-items.h" +#include "printtext.h" + +static int queryclose_tag, query_auto_close, querycreate_level; + +/* Return query where to put the private message. */ +QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick, + int own, int level) +{ + QUERY_REC *query; + + g_return_val_if_fail(IS_SERVER(server), NULL); + g_return_val_if_fail(nick != NULL, NULL); + + query = query_find(server, nick); + if (query == NULL && (querycreate_level & level) != 0 && + (!own || settings_get_bool("autocreate_own_query"))) { + query = CHAT_PROTOCOL(server)-> + query_create(server->tag, nick, TRUE); + } + + return query; +} + +static void signal_query_created(QUERY_REC *query, gpointer automatic) +{ + g_return_if_fail(IS_QUERY(query)); + + if (window_item_window(query) == NULL) { + window_item_create((WI_ITEM_REC *) query, + GPOINTER_TO_INT(automatic)); + printformat(query->server, query->name, MSGLEVEL_CLIENTNOTICE, + TXT_QUERY_STARTED, query->name); + } +} + +static void signal_query_created_curwin(QUERY_REC *query) +{ + g_return_if_fail(IS_QUERY(query)); + + window_item_add(active_win, (WI_ITEM_REC *) query, FALSE); +} + +static void signal_query_destroyed(QUERY_REC *query) +{ + WINDOW_REC *window; + + g_return_if_fail(IS_QUERY(query)); + + window = window_item_window((WI_ITEM_REC *) query); + if (window != NULL) { + window_item_destroy((WI_ITEM_REC *) query); + + if (!query->unwanted) + window_auto_destroy(window); + } +} + +static void signal_query_server_changed(QUERY_REC *query) +{ + WINDOW_REC *window; + + g_return_if_fail(query != NULL); + + window = window_item_window((WI_ITEM_REC *) query); + if (window->active == (WI_ITEM_REC *) query) + window_change_server(window, query->server); +} + +static void signal_query_nick_changed(QUERY_REC *query, const char *oldnick) +{ + g_return_if_fail(query != NULL); + + signal_emit("window item changed", 2, + window_item_window((WI_ITEM_REC *) query), query); +} + +static void signal_window_item_server_changed(WINDOW_REC *window, + QUERY_REC *query) +{ + if (IS_QUERY(query)) { + g_free_and_null(query->server_tag); + if (query->server != NULL) + query->server_tag = g_strdup(query->server->tag); + } +} + +static void signal_window_item_destroy(WINDOW_REC *window, WI_ITEM_REC *item) +{ + QUERY_REC *query; + + g_return_if_fail(window != NULL); + + query = QUERY(item); + if (query != NULL) query_destroy(query); +} + +static void sig_server_connected(SERVER_REC *server) +{ + GSList *tmp; + + if (!IS_SERVER(server)) + return; + + /* check if there's any queries without server */ + for (tmp = queries; tmp != NULL; tmp = tmp->next) { + QUERY_REC *rec = tmp->data; + + if (rec->server == NULL && + (rec->server_tag == NULL || + g_strcasecmp(rec->server_tag, server->tag) == 0)) { + window_item_change_server((WI_ITEM_REC *) rec, server); + server->queries = g_slist_append(server->queries, rec); + } + } +} + +static void cmd_window_server(const char *data) +{ + SERVER_REC *server; + QUERY_REC *query; + + g_return_if_fail(data != NULL); + + server = server_find_tag(data); + query = QUERY(active_win->active); + if (server == NULL || query == NULL) + return; + + /* /WINDOW SERVER used in a query window */ + query_change_server(query, server); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_QUERY_SERVER_CHANGED, + query->name, server->tag); + signal_stop(); +} + +/* SYNTAX: UNQUERY [] */ +static void cmd_unquery(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + QUERY_REC *query; + + g_return_if_fail(data != NULL); + + if (*data == '\0') { + /* remove current query */ + query = QUERY(item); + if (query == NULL) return; + } else { + query = query_find(server, data); + if (query == NULL) { + printformat(server, NULL, MSGLEVEL_CLIENTERROR, + TXT_NO_QUERY, data); + return; + } + } + + query_destroy(query); +} + +/* SYNTAX: QUERY [-window] [-] [] */ +static void cmd_query(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + GHashTable *optlist; + QUERY_REC *query; + char *nick, *msg; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (*data == '\0') { + /* remove current query */ + cmd_unquery("", server, item); + return; + } + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | + PARAM_FLAG_OPTIONS | PARAM_FLAG_UNKNOWN_OPTIONS, + "query", &optlist, &nick, &msg)) + return; + if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + server = cmd_options_get_server("query", optlist, server); + if (server == NULL) { + cmd_params_free(free_arg); + return; + } + + if (*nick != '=' && (server == NULL || !server->connected)) + cmd_param_error(CMDERR_NOT_CONNECTED); + + if (g_hash_table_lookup(optlist, "window") != NULL) { + signal_add("query created", + (SIGNAL_FUNC) signal_query_created_curwin); + } + + query = query_find(server, nick); + if (query == NULL) + CHAT_PROTOCOL(server)->query_create(server->tag, nick, FALSE); + else { + /* query already exists */ + WINDOW_REC *window = window_item_window(query); + + if (window == active_win) { + /* query is in active window, set it active */ + window_item_set_active(active_win, + (WI_ITEM_REC *) query); + } else { + /* notify user how to move the query to active + window. this was used to be done automatically + but it just confused everyone who did it + accidentally */ + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_QUERY_MOVE_NOTIFY, query->name, + window->refnum); + } + } + + if (g_hash_table_lookup(optlist, "window") != NULL) { + signal_remove("query created", + (SIGNAL_FUNC) signal_query_created_curwin); + } + + if (*msg != '\0') { + /* FIXME: we'll need some function that does both + of these. and separate the , and . target handling + from own_private messagge.. */ + server->send_message(server, nick, msg); + + signal_emit("message own_private", 4, + server, msg, nick, nick); + } + + cmd_params_free(free_arg); +} + +static int window_has_query(WINDOW_REC *window) +{ + GSList *tmp; + + g_return_val_if_fail(window != NULL, FALSE); + + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + if (IS_QUERY(tmp->data)) + return TRUE; + } + + return FALSE; +} + +static void sig_window_changed(WINDOW_REC *window, WINDOW_REC *old_window) +{ + if (query_auto_close <= 0) + return; + + /* reset the window's last_line timestamp so that query doesn't get + closed immediately after switched to the window, or after changed + to some other window from it */ + if (window != NULL && window_has_query(window)) + window->last_line = time(NULL); + if (old_window != NULL && window_has_query(old_window)) + old_window->last_line = time(NULL); +} + +static int sig_query_autoclose(void) +{ + WINDOW_REC *window; + GSList *tmp, *next; + time_t now; + + now = time(NULL); + for (tmp = queries; tmp != NULL; tmp = next) { + QUERY_REC *rec = tmp->data; + + next = tmp->next; + window = window_item_window((WI_ITEM_REC *) rec); + if (window != active_win && rec->data_level == 0 && + now-window->last_line > query_auto_close) + query_destroy(rec); + } + return 1; +} + +static void sig_message_private(SERVER_REC *server, const char *msg, + const char *nick, const char *address) +{ + /* create query window if needed */ + privmsg_get_query(server, nick, FALSE, MSGLEVEL_MSGS); +} + +static void read_settings(void) +{ + querycreate_level = level2bits(settings_get_str("autocreate_query_level")); + query_auto_close = settings_get_int("autoclose_query"); + if (query_auto_close > 0 && queryclose_tag == -1) + queryclose_tag = g_timeout_add(5000, (GSourceFunc) sig_query_autoclose, NULL); + else if (query_auto_close <= 0 && queryclose_tag != -1) { + g_source_remove(queryclose_tag); + queryclose_tag = -1; + } +} + +void fe_queries_init(void) +{ + settings_add_str("lookandfeel", "autocreate_query_level", "MSGS DCCMSGS"); + settings_add_bool("lookandfeel", "autocreate_own_query", TRUE); + settings_add_int("lookandfeel", "autoclose_query", 0); + + queryclose_tag = -1; + read_settings(); + + signal_add("query created", (SIGNAL_FUNC) signal_query_created); + signal_add("query destroyed", (SIGNAL_FUNC) signal_query_destroyed); + signal_add("query server changed", (SIGNAL_FUNC) signal_query_server_changed); + signal_add("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed); + signal_add("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed); + signal_add_last("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy); + signal_add("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_add("window changed", (SIGNAL_FUNC) sig_window_changed); + signal_add_first("message private", (SIGNAL_FUNC) sig_message_private); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + command_bind("query", NULL, (SIGNAL_FUNC) cmd_query); + command_bind("unquery", NULL, (SIGNAL_FUNC) cmd_unquery); + command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server); + + command_set_options("query", "window"); +} + +void fe_queries_deinit(void) +{ + if (queryclose_tag != -1) g_source_remove(queryclose_tag); + + signal_remove("query created", (SIGNAL_FUNC) signal_query_created); + signal_remove("query destroyed", (SIGNAL_FUNC) signal_query_destroyed); + signal_remove("query server changed", (SIGNAL_FUNC) signal_query_server_changed); + signal_remove("query nick changed", (SIGNAL_FUNC) signal_query_nick_changed); + signal_remove("window item server changed", (SIGNAL_FUNC) signal_window_item_server_changed); + signal_remove("window item destroy", (SIGNAL_FUNC) signal_window_item_destroy); + signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed); + signal_remove("message private", (SIGNAL_FUNC) sig_message_private); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + + command_unbind("query", (SIGNAL_FUNC) cmd_query); + command_unbind("unquery", (SIGNAL_FUNC) cmd_unquery); + command_unbind("window server", (SIGNAL_FUNC) cmd_window_server); +} diff --git a/apps/irssi/src/fe-common/core/fe-queries.h b/apps/irssi/src/fe-common/core/fe-queries.h new file mode 100644 index 00000000..6db9cb44 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-queries.h @@ -0,0 +1,13 @@ +#ifndef __FE_QUERIES_H +#define __FE_QUERIES_H + +#include "queries.h" + +/* Return query where to put the private message. */ +QUERY_REC *privmsg_get_query(SERVER_REC *server, const char *nick, + int own, int level); + +void fe_queries_init(void); +void fe_queries_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/fe-server.c b/apps/irssi/src/fe-common/core/fe-server.c new file mode 100644 index 00000000..f2327c59 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-server.c @@ -0,0 +1,363 @@ +/* + fe-server.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "commands.h" +#include "network.h" +#include "levels.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "chatnets.h" +#include "servers.h" +#include "servers-setup.h" +#include "servers-reconnect.h" + +#include "module-formats.h" +#include "printtext.h" + +static void print_servers(void) +{ + GSList *tmp; + + for (tmp = servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *rec = tmp->data; + + printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LIST, + rec->tag, rec->connrec->address, rec->connrec->port, + rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, rec->connrec->nick); + } +} + +static void print_lookup_servers(void) +{ + GSList *tmp; + for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) { + SERVER_REC *rec = tmp->data; + + printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_LOOKUP_LIST, + rec->tag, rec->connrec->address, rec->connrec->port, + rec->connrec->chatnet == NULL ? "" : rec->connrec->chatnet, rec->connrec->nick); + } +} + +static void print_reconnects(void) +{ + GSList *tmp; + char *tag, *next_connect; + int left; + + for (tmp = reconnects; tmp != NULL; tmp = tmp->next) { + RECONNECT_REC *rec = tmp->data; + SERVER_CONNECT_REC *conn = rec->conn; + + tag = g_strdup_printf("RECON-%d", rec->tag); + left = rec->next_connect-time(NULL); + next_connect = g_strdup_printf("%02d:%02d", left/60, left%60); + printformat(NULL, NULL, MSGLEVEL_CRAP, TXT_SERVER_RECONNECT_LIST, + tag, conn->address, conn->port, + conn->chatnet == NULL ? "" : conn->chatnet, + conn->nick, next_connect); + g_free(next_connect); + g_free(tag); + } +} + +static SERVER_SETUP_REC *create_server_setup(GHashTable *optlist) +{ + CHAT_PROTOCOL_REC *rec; + SERVER_SETUP_REC *server; + char *chatnet; + + rec = chat_protocol_find_net(optlist); + if (rec == NULL) + rec = chat_protocol_get_default(); + else { + chatnet = g_hash_table_lookup(optlist, rec->chatnet); + if (chatnet_find(chatnet) == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_UNKNOWN_CHATNET, chatnet); + return NULL; + } + } + + server = rec->create_server_setup(); + server->chat_type = rec->id; + return server; +} + +static void cmd_server_add(const char *data) +{ + GHashTable *optlist; + SERVER_SETUP_REC *rec; + char *addr, *portstr, *password, *value; + void *free_arg; + int port; + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS, + "server add", &optlist, &addr, &portstr, &password)) + return; + + if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + port = *portstr == '\0' ? 6667 : atoi(portstr); + + rec = server_setup_find_port(addr, port); + if (rec == NULL) { + rec = create_server_setup(optlist); + if (rec == NULL) { + cmd_params_free(free_arg); + return; + } + rec->address = g_strdup(addr); + rec->port = port; + } else { + value = g_hash_table_lookup(optlist, "port"); + if (value != NULL && *value != '\0') rec->port = atoi(value); + + if (*password != '\0') g_free_and_null(rec->password); + if (g_hash_table_lookup(optlist, "host")) { + g_free_and_null(rec->own_host); + rec->own_ip4 = rec->own_ip6 = NULL; + } + } + + if (g_hash_table_lookup(optlist, "6")) + rec->family = AF_INET6; + else if (g_hash_table_lookup(optlist, "4")) + rec->family = AF_INET; + + if (g_hash_table_lookup(optlist, "auto")) rec->autoconnect = TRUE; + if (g_hash_table_lookup(optlist, "noauto")) rec->autoconnect = FALSE; + + if (*password != '\0' && strcmp(password, "-") != 0) rec->password = g_strdup(password); + value = g_hash_table_lookup(optlist, "host"); + if (value != NULL && *value != '\0') { + rec->own_host = g_strdup(value); + rec->own_ip4 = rec->own_ip6 = NULL; + } + + signal_emit("server add fill", 2, rec, optlist); + + server_setup_add(rec); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_SETUPSERVER_ADDED, addr, port); + + cmd_params_free(free_arg); +} + +/* SYNTAX: SERVER REMOVE
[] */ +static void cmd_server_remove(const char *data) +{ + SERVER_SETUP_REC *rec; + char *addr, *port; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 2, &addr, &port)) + return; + if (*addr == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (*port == '\0') + rec = server_setup_find(addr, -1); + else + rec = server_setup_find_port(addr, atoi(port)); + + if (rec == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_NOT_FOUND, addr, port); + else { + server_setup_remove(rec); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_REMOVED, addr, port); + } + + cmd_params_free(free_arg); +} + +static void cmd_server(const char *data, SERVER_REC *server, void *item) +{ + GHashTable *optlist; + char *addr; + void *free_arg; + + if (*data == '\0') { + if (servers == NULL && lookup_servers == NULL && + reconnects == NULL) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_NO_CONNECTED_SERVERS); + } else { + print_servers(); + print_lookup_servers(); + print_reconnects(); + } + + signal_stop(); + return; + } + + if (g_strncasecmp(data, "add ", 4) == 0 || + g_strncasecmp(data, "remove ", 7) == 0 || + g_strcasecmp(data, "list") == 0 || + g_strncasecmp(data, "list ", 5) == 0) { + command_runsub("server", data, server, item); + signal_stop(); + return; + } + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS, + "connect", &optlist, &addr)) + return; + + if (*addr == '\0' || strcmp(addr, "+") == 0) + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + if (*addr == '+') window_create(NULL, FALSE); + + cmd_params_free(free_arg); +} + +static void sig_server_looking(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_LOOKING_UP, server->connrec->address); +} + +static void sig_server_connecting(SERVER_REC *server, IPADDR *ip) +{ + char ipaddr[MAX_IP_LEN]; + + g_return_if_fail(server != NULL); + g_return_if_fail(ip != NULL); + + net_ip2host(ip, ipaddr); + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, TXT_CONNECTING, + server->connrec->address, ipaddr, server->connrec->port); +} + +static void sig_server_connected(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(server, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_CONNECTION_ESTABLISHED, server->connrec->address); +} + +static void sig_connect_failed(SERVER_REC *server, gchar *msg) +{ + g_return_if_fail(server != NULL); + + if (msg == NULL) { + /* no message so this wasn't unexpected fail - send + connection_lost message instead */ + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_CONNECTION_LOST, server->connrec->address); + } else { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_CANT_CONNECT, server->connrec->address, server->connrec->port, msg); + } +} + +static void sig_server_disconnected(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_CONNECTION_LOST, server->connrec->address); +} + +static void sig_server_quit(SERVER_REC *server, const char *msg) +{ + g_return_if_fail(server != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_SERVER_QUIT, server->connrec->address, msg); +} + +static void sig_server_lag_disconnected(SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_LAG_DISCONNECTED, server->connrec->address, time(NULL)-server->lag_sent); +} + +static void sig_server_reconnect_removed(RECONNECT_REC *reconnect) +{ + g_return_if_fail(reconnect != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_RECONNECT_REMOVED, reconnect->conn->address, reconnect->conn->port, + reconnect->conn->chatnet == NULL ? "" : reconnect->conn->chatnet); +} + +static void sig_server_reconnect_not_found(const char *tag) +{ + g_return_if_fail(tag != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_RECONNECT_NOT_FOUND, tag); +} + +static void sig_chat_protocol_unknown(const char *protocol) +{ + g_return_if_fail(protocol != NULL); + + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_UNKNOWN_CHAT_PROTOCOL, protocol); +} + +void fe_server_init(void) +{ + command_bind("server", NULL, (SIGNAL_FUNC) cmd_server); + command_bind("server add", NULL, (SIGNAL_FUNC) cmd_server_add); + command_bind("server remove", NULL, (SIGNAL_FUNC) cmd_server_remove); + command_set_options("server add", "4 6 auto noauto -host -port"); + + signal_add("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_add("server connecting", (SIGNAL_FUNC) sig_server_connecting); + signal_add("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_add("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("server quit", (SIGNAL_FUNC) sig_server_quit); + + signal_add("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected); + signal_add("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed); + signal_add("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found); + + signal_add("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown); +} + +void fe_server_deinit(void) +{ + command_unbind("server", (SIGNAL_FUNC) cmd_server); + command_unbind("server add", (SIGNAL_FUNC) cmd_server_add); + command_unbind("server remove", (SIGNAL_FUNC) cmd_server_remove); + + signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_remove("server connecting", (SIGNAL_FUNC) sig_server_connecting); + signal_remove("server connected", (SIGNAL_FUNC) sig_server_connected); + signal_remove("server connect failed", (SIGNAL_FUNC) sig_connect_failed); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit); + + signal_remove("server lag disconnect", (SIGNAL_FUNC) sig_server_lag_disconnected); + signal_remove("server reconnect remove", (SIGNAL_FUNC) sig_server_reconnect_removed); + signal_remove("server reconnect not found", (SIGNAL_FUNC) sig_server_reconnect_not_found); + + signal_remove("chat protocol unknown", (SIGNAL_FUNC) sig_chat_protocol_unknown); +} diff --git a/apps/irssi/src/fe-common/core/fe-settings.c b/apps/irssi/src/fe-common/core/fe-settings.c new file mode 100644 index 00000000..0d8aaa93 --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-settings.c @@ -0,0 +1,330 @@ +/* + fe-settings.c : irssi + + Copyright (C) 1999 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "servers.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "levels.h" +#include "printtext.h" +#include "keyboard.h" + +static void set_print(SETTINGS_REC *rec) +{ + const char *value; + char value_int[MAX_INT_STRLEN]; + + switch (rec->type) { + case SETTING_TYPE_BOOLEAN: + value = settings_get_bool(rec->key) ? "ON" : "OFF"; + break; + case SETTING_TYPE_INT: + ltoa(value_int, settings_get_int(rec->key)); + value = value_int; + break; + case SETTING_TYPE_STRING: + value = settings_get_str(rec->key); + break; + default: + value = ""; + } + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%s = %s", rec->key, value); +} + +static void set_boolean(const char *key, const char *value) +{ + if (g_strcasecmp(value, "ON") == 0) + settings_set_bool(key, TRUE); + else if (g_strcasecmp(value, "OFF") == 0) + settings_set_bool(key, FALSE); + else if (g_strcasecmp(value, "TOGGLE") == 0) + settings_set_bool(key, !settings_get_bool(key)); + else + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_NOT_TOGGLE); +} + +/* SYNTAX: SET [-clear] [ []] */ +static void cmd_set(char *data) +{ + GHashTable *optlist; + GSList *sets, *tmp; + const char *last_section; + char *key, *value; + void *free_arg; + int found, clear; + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS, + "set", &optlist, &key, &value)) + return; + + clear = g_hash_table_lookup(optlist, "clear") != NULL; + + last_section = ""; found = 0; + sets = settings_get_sorted(); + for (tmp = sets; tmp != NULL; tmp = tmp->next) { + SETTINGS_REC *rec = tmp->data; + + if (((clear || *value != '\0') && g_strcasecmp(rec->key, key) != 0) || + (*value == '\0' && *key != '\0' && stristr(rec->key, key) == NULL)) + continue; + + if (strcmp(last_section, rec->section) != 0) { + /* print section */ + printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP, "%_[ %s ]", rec->section); + last_section = rec->section; + } + + if (clear || *value != '\0') { + /* change the setting */ + switch (rec->type) { + case SETTING_TYPE_BOOLEAN: + if (clear) + settings_set_bool(key, FALSE); + else + set_boolean(key, value); + break; + case SETTING_TYPE_INT: + settings_set_int(key, clear ? 0 : atoi(value)); + break; + case SETTING_TYPE_STRING: + settings_set_str(key, clear ? "" : value); + break; + } + signal_emit("setup changed", 0); + } + + set_print(rec); + found = TRUE; + + if (clear || *value != '\0') + break; + } + g_slist_free(sets); + + if (!found) + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown setting %s", key); + + cmd_params_free(free_arg); +} + +/* SYNTAX: TOGGLE [on|off|toggle] */ +static void cmd_toggle(const char *data) +{ + char *key, *value; + void *free_arg; + int type; + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &key, &value)) + return; + + if (*key == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + type = settings_get_type(key); + if (type == -1) + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown setting %_%s", key); + else if (type != SETTING_TYPE_BOOLEAN) + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Setting %_%s%_ isn't boolean, use /SET", key); + else { + set_boolean(key, *value != '\0' ? value : "TOGGLE"); + set_print(settings_get_record(key)); + } + + cmd_params_free(free_arg); +} + +static int config_key_compare(CONFIG_NODE *node1, CONFIG_NODE *node2) +{ + return g_strcasecmp(node1->key, node2->key); +} + +static void show_aliases(const char *alias) +{ + CONFIG_NODE *node; + GSList *tmp, *list; + int aliaslen; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_HEADER); + + node = iconfig_node_traverse("aliases", FALSE); + tmp = node == NULL ? NULL : node->value; + + /* first get the list of aliases sorted */ + list = NULL; + aliaslen = strlen(alias); + for (; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + if (node->type != NODE_TYPE_KEY) + continue; + + if (aliaslen != 0 && g_strncasecmp(node->key, alias, aliaslen) != 0) + continue; + + list = g_slist_insert_sorted(list, node, (GCompareFunc) config_key_compare); + } + + /* print the aliases */ + for (tmp = list; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_LINE, + node->key, node->value); + } + g_slist_free(list); + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_ALIASLIST_FOOTER); +} + +static void alias_remove(const char *alias) +{ + if (iconfig_get_str("aliases", alias, NULL) == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_NOT_FOUND, alias); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_REMOVED, alias); + iconfig_set_str("aliases", alias, NULL); + } +} + +/* SYNTAX: ALIAS [[-] []] */ +static void cmd_alias(const char *data) +{ + char *alias, *value; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &alias, &value)) + return; + + if (*alias == '-') { + if (alias[1] != '\0') alias_remove(alias+1); + } else if (*alias == '\0' || *value == '\0') + show_aliases(alias); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_ALIAS_ADDED, alias); + iconfig_set_str("aliases", alias, value); + } + cmd_params_free(free_arg); +} + +/* SYNTAX: UNALIAS */ +static void cmd_unalias(const char *data) +{ + g_return_if_fail(data != NULL); + if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + alias_remove(data); +} + +/* SYNTAX: RELOAD [] */ +static void cmd_reload(const char *data) +{ + char *fname; + + fname = *data != '\0' ? g_strdup(data) : + g_strdup_printf("%s/.silc/config", g_get_home_dir()); + if (settings_reread(fname)) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_CONFIG_RELOADED, fname); + } + g_free(fname); +} + +static void settings_save_fe(const char *fname) +{ + if (settings_save(fname)) { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_CONFIG_SAVED, fname); + } +} + +static void settings_save_confirm(const char *line, char *fname) +{ + if (line[0] == 'Y') + settings_save_fe(fname); + g_free(fname); +} + +/* SYNTAX: SAVE [] */ +static void cmd_save(const char *data) +{ + char *format; + + if (*data == '\0') + data = mainconfig->fname; + + if (!irssi_config_is_changed(data)) { + settings_save_fe(data); + return; + } + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_CONFIG_MODIFIED, data); + + format = format_get_text(MODULE_NAME, NULL, NULL, NULL, + TXT_OVERWRITE_CONFIG); + keyboard_entry_redirect((SIGNAL_FUNC) settings_save_confirm, + format, 0, g_strdup(data)); + g_free(format); +} + +static void settings_clean_confirm(const char *line) +{ + if (line[0] == 'Y') + settings_clean_invalid(); +} + +static void sig_settings_errors(const char *msg) +{ + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", msg); + keyboard_entry_redirect((SIGNAL_FUNC) settings_clean_confirm, + "Remove unknown settings from config file (Y/n)?", + 0, NULL); +} + +void fe_settings_init(void) +{ + command_bind("set", NULL, (SIGNAL_FUNC) cmd_set); + command_bind("toggle", NULL, (SIGNAL_FUNC) cmd_toggle); + command_bind("alias", NULL, (SIGNAL_FUNC) cmd_alias); + command_bind("unalias", NULL, (SIGNAL_FUNC) cmd_unalias); + command_bind("reload", NULL, (SIGNAL_FUNC) cmd_reload); + command_bind("save", NULL, (SIGNAL_FUNC) cmd_save); + command_set_options("set", "clear"); + + signal_add("settings errors", (SIGNAL_FUNC) sig_settings_errors); +} + +void fe_settings_deinit(void) +{ + command_unbind("set", (SIGNAL_FUNC) cmd_set); + command_unbind("toggle", (SIGNAL_FUNC) cmd_toggle); + command_unbind("alias", (SIGNAL_FUNC) cmd_alias); + command_unbind("unalias", (SIGNAL_FUNC) cmd_unalias); + command_unbind("reload", (SIGNAL_FUNC) cmd_reload); + command_unbind("save", (SIGNAL_FUNC) cmd_save); + + signal_remove("settings errors", (SIGNAL_FUNC) sig_settings_errors); +} diff --git a/apps/irssi/src/fe-common/core/fe-windows.c b/apps/irssi/src/fe-common/core/fe-windows.c new file mode 100644 index 00000000..3c48261a --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-windows.c @@ -0,0 +1,583 @@ +/* + windows.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "servers.h" +#include "misc.h" +#include "settings.h" + +#include "levels.h" + +#include "printtext.h" +#include "fe-windows.h" +#include "window-items.h" + +GSList *windows; /* first in the list is the active window, + next is the last active, etc. */ +WINDOW_REC *active_win; + +static int daytag; +static int daycheck; /* 0 = don't check, 1 = time is 00:00, check, + 2 = time is 00:00, already checked */ + +static int window_get_new_refnum(void) +{ + WINDOW_REC *win; + GSList *tmp; + int refnum; + + refnum = 1; + tmp = windows; + while (tmp != NULL) { + win = tmp->data; + + if (refnum != win->refnum) { + tmp = tmp->next; + continue; + } + + refnum++; + tmp = windows; + } + + return refnum; +} + +WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic) +{ + WINDOW_REC *rec; + + rec = g_new0(WINDOW_REC, 1); + rec->refnum = window_get_new_refnum(); + + windows = g_slist_prepend(windows, rec); + signal_emit("window created", 2, rec, GINT_TO_POINTER(automatic)); + + if (item != NULL) window_item_add(rec, item, automatic); + if (windows->next == NULL || !automatic || settings_get_bool("window_auto_change")) { + if (automatic && windows->next != NULL) + signal_emit("window changed automatic", 1, rec); + window_set_active(rec); + } + return rec; +} + +/* removed_refnum was removed from the windows list, pack the windows so + there won't be any holes. If there is any holes after removed_refnum, + leave the windows behind it alone. */ +static void windows_pack(int removed_refnum) +{ + WINDOW_REC *window; + int refnum; + + for (refnum = removed_refnum+1;; refnum++) { + window = window_find_refnum(refnum); + if (window == NULL || window->sticky_refnum) + break; + + window_set_refnum(window, refnum-1); + } +} + +void window_destroy(WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + + if (window->destroying) return; + window->destroying = TRUE; + windows = g_slist_remove(windows, window); + + if (active_win == window && windows != NULL) { + active_win = NULL; /* it's corrupted */ + window_set_active(windows->data); + } + + while (window->items != NULL) + window_item_destroy(window->items->data); + + if (settings_get_bool("windows_auto_renumber")) + windows_pack(window->refnum); + + signal_emit("window destroyed", 1, window); + + while (window->bound_items != NULL) + window_bind_destroy(window, window->bound_items->data); + + g_free_not_null(window->hilight_color); + g_free_not_null(window->servertag); + g_free_not_null(window->theme_name); + g_free_not_null(window->name); + g_free(window); +} + +void window_auto_destroy(WINDOW_REC *window) +{ + if (settings_get_bool("autoclose_windows") && windows->next != NULL && + window->items == NULL && window->level == 0) + window_destroy(window); +} + +void window_set_active(WINDOW_REC *window) +{ + WINDOW_REC *old_window; + + if (window == active_win) + return; + + old_window = active_win; + active_win = window; + if (active_win != NULL) { + windows = g_slist_remove(windows, active_win); + windows = g_slist_prepend(windows, active_win); + } + + if (active_win != NULL) + signal_emit("window changed", 2, active_win, old_window); +} + +void window_change_server(WINDOW_REC *window, void *server) +{ + window->active_server = server; + signal_emit("window server changed", 2, window, server); +} + +void window_set_refnum(WINDOW_REC *window, int refnum) +{ + GSList *tmp; + int old_refnum; + + g_return_if_fail(window != NULL); + g_return_if_fail(refnum >= 1); + if (window->refnum == refnum) return; + + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->refnum == refnum) { + rec->refnum = window->refnum; + signal_emit("window refnum changed", 2, rec, GINT_TO_POINTER(refnum)); + break; + } + } + + old_refnum = window->refnum; + window->refnum = refnum; + signal_emit("window refnum changed", 2, window, GINT_TO_POINTER(old_refnum)); +} + +void window_set_name(WINDOW_REC *window, const char *name) +{ + g_free_not_null(window->name); + window->name = g_strdup(name); + + signal_emit("window name changed", 1, window); +} + +void window_set_level(WINDOW_REC *window, int level) +{ + g_return_if_fail(window != NULL); + + window->level = level; + signal_emit("window level changed", 1, window); +} + +/* return active item's name, or if none is active, window's name */ +char *window_get_active_name(WINDOW_REC *window) +{ + g_return_val_if_fail(window != NULL, NULL); + + if (window->active != NULL) + return window->active->name; + + return window->name; +} + +WINDOW_REC *window_find_level(void *server, int level) +{ + WINDOW_REC *match; + GSList *tmp; + + match = NULL; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if ((server == NULL || rec->active_server == server) && + (rec->level & level)) { + if (server == NULL || rec->active_server == server) + return rec; + match = rec; + } + } + + return match; +} + +WINDOW_REC *window_find_closest(void *server, const char *name, int level) +{ + WINDOW_REC *window; + WI_ITEM_REC *item; + + /* match by name */ + item = name == NULL ? NULL : + window_item_find(server, name); + if (item != NULL) + return window_item_window(item); + + /* match by level */ + if (level != MSGLEVEL_HILIGHT) + level &= ~(MSGLEVEL_HILIGHT | MSGLEVEL_NOHILIGHT); + window = window_find_level(server, level); + if (window != NULL) return window; + + /* match by level - ignore server */ + window = window_find_level(NULL, level); + if (window != NULL) return window; + + /* fallback to active */ + return active_win; +} + +WINDOW_REC *window_find_refnum(int refnum) +{ + GSList *tmp; + + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->refnum == refnum) + return rec; + } + + return NULL; +} + +WINDOW_REC *window_find_name(const char *name) +{ + GSList *tmp; + + g_return_val_if_fail(name != NULL, NULL); + + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->name != NULL && g_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +WINDOW_REC *window_find_item(SERVER_REC *server, const char *name) +{ + WINDOW_REC *rec; + WI_ITEM_REC *item; + + g_return_val_if_fail(name != NULL, NULL); + + rec = window_find_name(name); + if (rec != NULL) return rec; + + item = server == NULL ? NULL : + window_item_find(server, name); + if (item == NULL && server == NULL) { + /* not found from the active server - any server? */ + item = window_item_find(NULL, name); + } + + if (item == NULL) { + char *chan; + + /* still nothing? maybe user just left the # in front of + channel, try again with it.. */ + chan = g_strdup_printf("#%s", name); + item = server == NULL ? NULL : + window_item_find(server, chan); + if (item == NULL) item = window_item_find(NULL, chan); + g_free(chan); + } + + if (item == NULL) + return 0; + + return window_item_window(item); +} + +int window_refnum_prev(int refnum, int wrap) +{ + GSList *tmp; + int prev, max; + + max = prev = -1; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->refnum < refnum && (prev == -1 || rec->refnum > prev)) + prev = rec->refnum; + if (wrap && (max == -1 || rec->refnum > max)) + max = rec->refnum; + } + + return prev != -1 ? prev : max; +} + +int window_refnum_next(int refnum, int wrap) +{ + GSList *tmp; + int min, next; + + min = next = -1; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->refnum > refnum && (next == -1 || rec->refnum < next)) + next = rec->refnum; + if (wrap && (min == -1 || rec->refnum < min)) + min = rec->refnum; + } + + return next != -1 ? next : min; +} + +int windows_refnum_last(void) +{ + GSList *tmp; + int max; + + max = -1; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->refnum > max) + max = rec->refnum; + } + + return max; +} + +static int window_refnum_cmp(WINDOW_REC *w1, WINDOW_REC *w2) +{ + return w1->refnum < w2->refnum ? -1 : 1; +} + +GSList *windows_get_sorted(void) +{ + GSList *tmp, *sorted; + + sorted = NULL; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + sorted = g_slist_insert_sorted(sorted, rec, (GCompareFunc) + window_refnum_cmp); + } + + return sorted; +} + +WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag, + const char *name) +{ + WINDOW_BIND_REC *rec; + + g_return_val_if_fail(window != NULL, NULL); + g_return_val_if_fail(servertag != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + rec = g_new0(WINDOW_BIND_REC, 1); + rec->name = g_strdup(name); + rec->servertag = g_strdup(servertag); + + window->bound_items = g_slist_append(window->bound_items, rec); + return rec; +} + +void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec) +{ + g_return_if_fail(window != NULL); + g_return_if_fail(rec != NULL); + + window->bound_items = g_slist_remove(window->bound_items, rec); + + g_free(rec->servertag); + g_free(rec->name); + g_free(rec); +} + +WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag, + const char *name) +{ + GSList *tmp; + + g_return_val_if_fail(window != NULL, NULL); + g_return_val_if_fail(servertag != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) { + WINDOW_BIND_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, name) == 0 && + g_strcasecmp(rec->servertag, servertag) == 0) + return rec; + } + + return NULL; +} + +void window_bind_remove_unsticky(WINDOW_REC *window) +{ + GSList *tmp, *next; + + for (tmp = window->bound_items; tmp != NULL; tmp = next) { + WINDOW_BIND_REC *rec = tmp->data; + + next = tmp->next; + if (!rec->sticky) + window_bind_destroy(window, rec); + } +} + +static void sig_server_looking(SERVER_REC *server) +{ + GSList *tmp; + + g_return_if_fail(server != NULL); + + /* Try to keep some server assigned to windows.. + Also change active window's server if the window is empty */ + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if ((rec->servertag == NULL || + g_strcasecmp(rec->servertag, server->tag) == 0) && + (rec->active_server == NULL || + (rec == active_win && rec->items == NULL))) + window_change_server(rec, server); + } +} + +static void sig_server_disconnected(SERVER_REC *server) +{ + GSList *tmp; + SERVER_REC *new_server; + + g_return_if_fail(server != NULL); + + new_server = servers == NULL ? NULL : servers->data; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->active_server == server) { + window_change_server(rec, rec->servertag != NULL ? + NULL : new_server); + } + } +} + +static void sig_print_text(void) +{ + GSList *tmp; + char month[100]; + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + if (strftime(month, sizeof(month), "%b", tm) <= 0) + month[0] = '\0'; + + if (tm->tm_hour != 0 || tm->tm_min != 0) + return; + + daycheck = 2; + signal_remove("print text", (SIGNAL_FUNC) sig_print_text); + + /* day changed, print notice about it to every window */ + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + printformat_window(tmp->data, MSGLEVEL_NEVER, TXT_DAYCHANGE, + tm->tm_mday, tm->tm_mon+1, + 1900+tm->tm_year, month); + } +} + +static int sig_check_daychange(void) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + + if (daycheck == 1 && tm->tm_hour == 0 && tm->tm_min == 0) { + sig_print_text(); + return TRUE; + } + + if (tm->tm_hour != 23 || tm->tm_min != 59) { + daycheck = 0; + return TRUE; + } + + /* time is 23:59 */ + if (daycheck == 0) { + daycheck = 1; + signal_add("print text", (SIGNAL_FUNC) sig_print_text); + } + return TRUE; +} + +static void read_settings(void) +{ + if (daytag != -1) { + g_source_remove(daytag); + daytag = -1; + } + + if (settings_get_bool("timestamps")) + daytag = g_timeout_add(30000, (GSourceFunc) sig_check_daychange, NULL); +} + +void windows_init(void) +{ + active_win = NULL; + daycheck = 0; daytag = -1; + settings_add_bool("lookandfeel", "window_auto_change", FALSE); + settings_add_bool("lookandfeel", "windows_auto_renumber", TRUE); + + read_settings(); + signal_add("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("server connect failed", (SIGNAL_FUNC) sig_server_disconnected); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void windows_deinit(void) +{ + if (daytag != -1) g_source_remove(daytag); + if (daycheck == 1) signal_remove("print text", (SIGNAL_FUNC) sig_print_text); + + signal_remove("server looking", (SIGNAL_FUNC) sig_server_looking); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("server connect failed", (SIGNAL_FUNC) sig_server_disconnected); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/fe-common/core/fe-windows.h b/apps/irssi/src/fe-common/core/fe-windows.h new file mode 100644 index 00000000..c9ffe69a --- /dev/null +++ b/apps/irssi/src/fe-common/core/fe-windows.h @@ -0,0 +1,96 @@ +#ifndef __WINDOWS_H +#define __WINDOWS_H + +#include "servers.h" + +#define STRUCT_SERVER_REC SERVER_REC +#include "window-item-def.h" + +enum { + DATA_LEVEL_NONE = 0, + DATA_LEVEL_TEXT, + DATA_LEVEL_MSG, + DATA_LEVEL_HILIGHT +}; + +typedef struct { + char *servertag; + char *name; + unsigned int sticky:1; +} WINDOW_BIND_REC; + +typedef struct { + int refnum; + char *name; + + int width, height; + + GSList *items; + WI_ITEM_REC *active; + SERVER_REC *active_server; + char *servertag; /* active_server must be either NULL or have this tag (unless there's items in this window) */ + + int level; /* message level */ + GSList *bound_items; /* list of WINDOW_BIND_RECs */ + + unsigned int sticky_refnum:1; + unsigned int destroying:1; + + /* window-specific command line history */ + GList *history, *history_pos; + int history_lines, history_over_counter; + + int data_level; /* current data level */ + char *hilight_color; /* current hilight color in %format */ + + time_t last_timestamp; /* When was last timestamp printed */ + time_t last_line; /* When was last line printed */ + + char *theme_name; /* active theme in window, NULL = default */ + void *theme; /* THEME_REC */ + + void *gui_data; +} WINDOW_REC; + +extern GSList *windows; +extern WINDOW_REC *active_win; + +WINDOW_REC *window_create(WI_ITEM_REC *item, int automatic); +void window_destroy(WINDOW_REC *window); + +void window_auto_destroy(WINDOW_REC *window); + +void window_set_active(WINDOW_REC *window); +void window_change_server(WINDOW_REC *window, void *server); + +void window_set_refnum(WINDOW_REC *window, int refnum); +void window_set_name(WINDOW_REC *window, const char *name); +void window_set_level(WINDOW_REC *window, int level); + +/* return active item's name, or if none is active, window's name */ +char *window_get_active_name(WINDOW_REC *window); + +WINDOW_REC *window_find_level(void *server, int level); +WINDOW_REC *window_find_closest(void *server, const char *name, int level); +WINDOW_REC *window_find_refnum(int refnum); +WINDOW_REC *window_find_name(const char *name); +WINDOW_REC *window_find_item(SERVER_REC *server, const char *name); + +int window_refnum_prev(int refnum, int wrap); +int window_refnum_next(int refnum, int wrap); +int windows_refnum_last(void); + +GSList *windows_get_sorted(void); + +WINDOW_BIND_REC *window_bind_add(WINDOW_REC *window, const char *servertag, + const char *name); +void window_bind_destroy(WINDOW_REC *window, WINDOW_BIND_REC *rec); + +WINDOW_BIND_REC *window_bind_find(WINDOW_REC *window, const char *servertag, + const char *name); +void window_bind_remove_unsticky(WINDOW_REC *window); + +void windows_init(void); +void windows_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/formats.c b/apps/irssi/src/fe-common/core/formats.c new file mode 100644 index 00000000..b933c068 --- /dev/null +++ b/apps/irssi/src/fe-common/core/formats.c @@ -0,0 +1,941 @@ +/* + formats.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "special-vars.h" +#include "settings.h" + +#include "levels.h" + +#include "fe-windows.h" +#include "formats.h" +#include "themes.h" +#include "translation.h" + +static const char *format_backs = "04261537"; +static const char *format_fores = "kbgcrmyw"; +static const char *format_boldfores = "KBGCRMYW"; + +static int signal_gui_print_text; +static int hide_text_style, hide_server_tags; + +static int timestamps, msgs_timestamps; +static int timestamp_timeout; + +int format_find_tag(const char *module, const char *tag) +{ + FORMAT_REC *formats; + int n; + + formats = g_hash_table_lookup(default_formats, module); + if (formats == NULL) + return -1; + + for (n = 0; formats[n].def != NULL; n++) { + if (formats[n].tag != NULL && + g_strcasecmp(formats[n].tag, tag) == 0) + return n; + } + + return -1; +} + +int format_expand_styles(GString *out, char format) +{ + char *p; + + switch (format) { + case 'U': + /* Underline on/off */ + g_string_append_c(out, 4); + g_string_append_c(out, FORMAT_STYLE_UNDERLINE); + break; + case '9': + case '_': + /* bold on/off */ + g_string_append_c(out, 4); + g_string_append_c(out, FORMAT_STYLE_BOLD); + break; + case '8': + /* reverse */ + g_string_append_c(out, 4); + g_string_append_c(out, FORMAT_STYLE_REVERSE); + break; + case '%': + g_string_append_c(out, '%'); + break; + case ':': + /* Newline */ + g_string_append_c(out, '\n'); + break; + case '|': + /* Indent here */ + g_string_append_c(out, 4); + g_string_append_c(out, FORMAT_STYLE_INDENT); + break; + case 'F': + /* blink */ + g_string_append_c(out, 4); + g_string_append_c(out, FORMAT_STYLE_BLINK); + break; + case 'n': + case 'N': + /* default color */ + g_string_append_c(out, 4); + g_string_append_c(out, FORMAT_STYLE_DEFAULTS); + break; + default: + /* check if it's a background color */ + p = strchr(format_backs, format); + if (p != NULL) { + g_string_append_c(out, 4); + g_string_append_c(out, FORMAT_COLOR_NOCHANGE); + g_string_append_c(out, (char) ((int) (p-format_backs)+'0')); + break; + } + + /* check if it's a foreground color */ + if (format == 'p') format = 'm'; + p = strchr(format_fores, format); + if (p != NULL) { + g_string_append_c(out, 4); + g_string_append_c(out, (char) ((int) (p-format_fores)+'0')); + g_string_append_c(out, FORMAT_COLOR_NOCHANGE); + break; + } + + /* check if it's a bold foreground color */ + if (format == 'P') format = 'M'; + p = strchr(format_boldfores, format); + if (p != NULL) { + g_string_append_c(out, 4); + g_string_append_c(out, (char) (8+(int) (p-format_boldfores)+'0')); + g_string_append_c(out, FORMAT_COLOR_NOCHANGE); + break; + } + + return FALSE; + } + + return TRUE; +} + +void format_read_arglist(va_list va, FORMAT_REC *format, + char **arglist, int arglist_size, + char *buffer, int buffer_size) +{ + int num, len, bufpos; + + g_return_if_fail(format->params < arglist_size); + + bufpos = 0; + arglist[format->params] = NULL; + for (num = 0; num < format->params; num++) { + switch (format->paramtypes[num]) { + case FORMAT_STRING: + arglist[num] = (char *) va_arg(va, char *); + if (arglist[num] == NULL) { + g_warning("format_read_arglist() : parameter %d is NULL", num); + arglist[num] = ""; + } + break; + case FORMAT_INT: { + int d = (int) va_arg(va, int); + + if (bufpos >= buffer_size) { + arglist[num] = ""; + break; + } + + arglist[num] = buffer+bufpos; + len = g_snprintf(buffer+bufpos, buffer_size-bufpos, + "%d", d); + bufpos += len+1; + break; + } + case FORMAT_LONG: { + long l = (long) va_arg(va, long); + + if (bufpos >= buffer_size) { + arglist[num] = ""; + break; + } + + arglist[num] = buffer+bufpos; + len = g_snprintf(buffer+bufpos, buffer_size-bufpos, + "%ld", l); + bufpos += len+1; + break; + } + case FORMAT_FLOAT: { + double f = (double) va_arg(va, double); + + if (bufpos >= buffer_size) { + arglist[num] = ""; + break; + } + + arglist[num] = buffer+bufpos; + len = g_snprintf(buffer+bufpos, buffer_size-bufpos, + "%0.2f", f); + bufpos += len+1; + break; + } + } + } +} + +void format_create_dest(TEXT_DEST_REC *dest, + void *server, const char *target, + int level, WINDOW_REC *window) +{ + dest->server = server; + dest->target = target; + dest->level = level; + dest->window = window != NULL ? window : + window_find_closest(server, target, level); + + dest->hilight_priority = 0; + dest->hilight_color = NULL; +} + +/* Return length of text part in string (ie. without % codes) */ +int format_get_length(const char *str) +{ + GString *tmp; + int len; + + g_return_val_if_fail(str != NULL, 0); + + tmp = g_string_new(NULL); + len = 0; + while (*str != '\0') { + if (*str == '%' && str[1] != '\0') { + str++; + if (*str != '%' && format_expand_styles(tmp, *str)) { + str++; + continue; + } + + /* %% or unknown %code, written as-is */ + if (*str != '%') + len++; + } + + len++; + str++; + } + + g_string_free(tmp, TRUE); + return len; +} + +/* Return how many characters in `str' must be skipped before `len' + characters of text is skipped. Like strip_real_length(), except this + handles %codes. */ +int format_real_length(const char *str, int len) +{ + GString *tmp; + const char *start; + + g_return_val_if_fail(str != NULL, 0); + g_return_val_if_fail(len >= 0, 0); + + start = str; + tmp = g_string_new(NULL); + while (*str != '\0' && len > 0) { + if (*str == '%' && str[1] != '\0') { + str++; + if (*str != '%' && format_expand_styles(tmp, *str)) { + str++; + continue; + } + + /* %% or unknown %code, written as-is */ + if (*str != '%') { + if (--len == 0) + break; + } + } + + len--; + str++; + } + + g_string_free(tmp, TRUE); + return (int) (str-start); +} + +char *format_string_expand(const char *text) +{ + GString *out; + char code, *ret; + + g_return_val_if_fail(text != NULL, NULL); + + out = g_string_new(NULL); + + code = 0; + while (*text != '\0') { + if (code == '%') { + /* color code */ + if (!format_expand_styles(out, *text)) { + g_string_append_c(out, '%'); + g_string_append_c(out, '%'); + g_string_append_c(out, *text); + } + code = 0; + } else { + if (*text == '%') + code = *text; + else + g_string_append_c(out, *text); + } + + text++; + } + + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +static char *format_get_text_args(TEXT_DEST_REC *dest, + const char *text, char **arglist) +{ + GString *out; + char code, *ret; + int need_free; + + out = g_string_new(NULL); + + code = 0; + while (*text != '\0') { + if (code == '%') { + /* color code */ + if (!format_expand_styles(out, *text)) { + g_string_append_c(out, '%'); + g_string_append_c(out, '%'); + g_string_append_c(out, *text); + } + code = 0; + } else if (code == '$') { + /* argument */ + char *ret; + + ret = parse_special((char **) &text, + active_win == NULL ? NULL : + active_win->active_server, + active_win == NULL ? NULL : + active_win->active, arglist, + &need_free, NULL, 0); + + if (ret != NULL) { + /* string shouldn't end with \003 or it could + mess up the next one or two characters */ + int diff; + int len = strlen(ret); + while (len > 0 && ret[len-1] == 3) len--; + diff = strlen(ret)-len; + + g_string_append(out, ret); + if (diff > 0) + g_string_truncate(out, out->len-diff); + if (need_free) g_free(ret); + } + code = 0; + } else { + if (*text == '%' || *text == '$') + code = *text; + else + g_string_append_c(out, *text); + } + + text++; + } + + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +char *format_get_text_theme(THEME_REC *theme, const char *module, + TEXT_DEST_REC *dest, int formatnum, ...) +{ + va_list va; + char *str; + + if (theme == NULL) { + theme = dest->window->theme == NULL ? current_theme : + dest->window->theme; + } + + va_start(va, formatnum); + str = format_get_text_theme_args(theme, module, dest, formatnum, va); + va_end(va); + + return str; +} + +char *format_get_text_theme_args(THEME_REC *theme, const char *module, + TEXT_DEST_REC *dest, int formatnum, + va_list va) +{ + char *arglist[MAX_FORMAT_PARAMS]; + char buffer[DEFAULT_FORMAT_ARGLIST_SIZE]; + FORMAT_REC *formats; + + formats = g_hash_table_lookup(default_formats, module); + format_read_arglist(va, &formats[formatnum], + arglist, sizeof(arglist)/sizeof(char *), + buffer, sizeof(buffer)); + + return format_get_text_theme_charargs(theme, module, dest, + formatnum, arglist); +} + +char *format_get_text_theme_charargs(THEME_REC *theme, const char *module, + TEXT_DEST_REC *dest, int formatnum, + char **args) +{ + MODULE_THEME_REC *module_theme; + char *text; + + module_theme = g_hash_table_lookup(theme->modules, module); + if (module_theme == NULL) + return NULL; + + text = module_theme->expanded_formats[formatnum]; + return format_get_text_args(dest, text, args); +} + +char *format_get_text(const char *module, WINDOW_REC *window, + void *server, const char *target, + int formatnum, ...) +{ + TEXT_DEST_REC dest; + THEME_REC *theme; + va_list va; + char *str; + + format_create_dest(&dest, server, target, 0, window); + theme = dest.window->theme == NULL ? current_theme : + dest.window->theme; + + va_start(va, formatnum); + str = format_get_text_theme_args(theme, module, &dest, formatnum, va); + va_end(va); + + return str; +} + +/* add `linestart' to start of each line in `text'. `text' may contain + multiple lines separated with \n. */ +char *format_add_linestart(const char *text, const char *linestart) +{ + GString *str; + char *ret; + + if (linestart == NULL) + return g_strdup(text); + + if (strchr(text, '\n') == NULL) + return g_strconcat(linestart, text, NULL); + + str = g_string_new(linestart); + while (*text != '\0') { + g_string_append_c(str, *text); + if (*text == '\n') + g_string_append(str, linestart); + text++; + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +#define LINE_START_IRSSI_LEVEL \ + (MSGLEVEL_CLIENTERROR | MSGLEVEL_CLIENTNOTICE) + +#define NOT_LINE_START_LEVEL \ + (MSGLEVEL_NEVER | MSGLEVEL_LASTLOG | MSGLEVEL_CLIENTCRAP | \ + MSGLEVEL_MSGS | MSGLEVEL_PUBLIC | MSGLEVEL_DCC | MSGLEVEL_DCCMSGS | \ + MSGLEVEL_ACTIONS | MSGLEVEL_NOTICES | MSGLEVEL_SNOTES | MSGLEVEL_CTCPS) + +/* return the "-!- " text at the start of the line */ +char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest) +{ + int format; + + if (dest->level & LINE_START_IRSSI_LEVEL) + format = TXT_LINE_START_IRSSI; + else if ((dest->level & NOT_LINE_START_LEVEL) == 0) + format = TXT_LINE_START; + else + return NULL; + + return format_get_text_theme(theme, MODULE_NAME, dest, format); +} + +#define show_timestamp(level) \ + ((level & (MSGLEVEL_NEVER|MSGLEVEL_LASTLOG)) == 0 && \ + (timestamps || (msgs_timestamps && ((level) & MSGLEVEL_MSGS)))) + +static char *get_timestamp(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t) +{ + struct tm *tm; + int diff; + + if (!show_timestamp(dest->level)) + return NULL; + + if (timestamp_timeout > 0) { + diff = t - dest->window->last_timestamp; + dest->window->last_timestamp = t; + if (diff < timestamp_timeout) + return NULL; + } + + tm = localtime(&t); + return format_get_text_theme(theme, MODULE_NAME, dest, TXT_TIMESTAMP, + tm->tm_year+1900, + tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + +static char *get_server_tag(THEME_REC *theme, TEXT_DEST_REC *dest) +{ + SERVER_REC *server; + int count = 0; + + server = dest->server; + + if (server == NULL || hide_server_tags || + (dest->window->active != NULL && + dest->window->active->server == server)) + return NULL; + + if (servers != NULL) { + count++; + if (servers->next != NULL) + count++; + } + if (count < 2 && lookup_servers != NULL) { + count++; + if (lookup_servers->next != NULL) + count++; + } + + return count < 2 ? NULL : + format_get_text_theme(theme, MODULE_NAME, dest, + TXT_SERVERTAG, server->tag); +} + +char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t) +{ + char *timestamp, *servertag; + char *linestart; + + timestamp = get_timestamp(theme, dest, t); + servertag = get_server_tag(theme, dest); + + if (timestamp == NULL && servertag == NULL) + return NULL; + + linestart = g_strconcat(timestamp != NULL ? timestamp : "", + servertag, NULL); + + g_free_not_null(timestamp); + g_free_not_null(servertag); + return linestart; +} + +void format_newline(WINDOW_REC *window) +{ + g_return_if_fail(window != NULL); + + signal_emit_id(signal_gui_print_text, 6, window, + GINT_TO_POINTER(-1), GINT_TO_POINTER(-1), + GINT_TO_POINTER(PRINTFLAG_NEWLINE), + "", GINT_TO_POINTER(-1)); +} + +/* parse ANSI color string */ +static char *get_ansi_color(THEME_REC *theme, char *str, + int *fg_ret, int *bg_ret, int *flags_ret) +{ + static char ansitab[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + char *start; + int fg, bg, flags, num; + + if (*str != '[') + return str; + start = str++; + + fg = fg_ret == NULL || *fg_ret < 0 ? theme->default_color : *fg_ret; + bg = bg_ret == NULL || *bg_ret < 0 ? -1 : *bg_ret; + flags = flags_ret == NULL ? 0 : *flags_ret; + + num = 0; + for (;; str++) { + if (*str == '\0') return start; + + if (isdigit((int) *str)) { + num = num*10 + (*str-'0'); + continue; + } + + if (*str != ';' && *str != 'm') + return start; + + switch (num) { + case 0: + /* reset colors back to default */ + fg = theme->default_color; + bg = -1; + flags &= ~PRINTFLAG_INDENT; + break; + case 1: + /* hilight */ + flags |= PRINTFLAG_BOLD; + break; + case 5: + /* blink */ + flags |= PRINTFLAG_BLINK; + break; + case 7: + /* reverse */ + flags |= PRINTFLAG_REVERSE; + break; + default: + if (num >= 30 && num <= 37) + fg = (fg & 0xf8) | ansitab[num-30]; + if (num >= 40 && num <= 47) { + if (bg == -1) bg = 0; + bg = (bg & 0xf8) | ansitab[num-40]; + } + break; + } + num = 0; + + if (*str == 'm') { + if (fg_ret != NULL) *fg_ret = fg; + if (bg_ret != NULL) *bg_ret = bg; + if (flags_ret != NULL) *flags_ret = flags; + + str++; + break; + } + } + + return str; +} + +/* parse MIRC color string */ +static void get_mirc_color(const char **str, int *fg_ret, int *bg_ret) +{ + int fg, bg; + + fg = fg_ret == NULL ? -1 : *fg_ret; + bg = bg_ret == NULL ? -1 : *bg_ret; + + if (!isdigit((int) **str) && **str != ',') { + fg = -1; + bg = -1; + } else { + /* foreground color */ + if (**str != ',') { + fg = **str-'0'; + (*str)++; + if (isdigit((int) **str)) { + fg = fg*10 + (**str-'0'); + (*str)++; + } + } + if (**str == ',') { + /* background color */ + (*str)++; + if (!isdigit((int) **str)) + bg = -1; + else { + bg = **str-'0'; + (*str)++; + if (isdigit((int) **str)) { + bg = bg*10 + (**str-'0'); + (*str)++; + } + } + } + } + + if (fg_ret) *fg_ret = fg; + if (bg_ret) *bg_ret = bg; +} + +#define IS_COLOR_CODE(c) \ + ((c) == 2 || (c) == 3 || (c) == 4 || (c) == 6 || (c) == 7 || \ + (c) == 15 || (c) == 22 || (c) == 27 || (c) == 31) + +/* Return how many characters in `str' must be skipped before `len' + characters of text is skipped. */ +int strip_real_length(const char *str, int len, + int *last_color_pos, int *last_color_len) +{ + const char *start = str; + + if (last_color_pos != NULL) + *last_color_pos = -1; + if (last_color_len != NULL) + *last_color_len = -1; + + while (*str != '\0') { + if (*str == 3) { + const char *mircstart = str; + + if (last_color_pos != NULL) + *last_color_pos = (int) (str-start); + str++; + get_mirc_color(&str, NULL, NULL); + if (last_color_len != NULL) + *last_color_len = (int) (str-mircstart); + + } else if (*str == 4 && str[1] != '\0') { + if (str[1] < FORMAT_STYLE_SPECIAL && str[2] != '\0') { + if (last_color_pos != NULL) + *last_color_pos = (int) (str-start); + if (last_color_len != NULL) + *last_color_len = 3; + str++; + } else if (str[1] == FORMAT_STYLE_DEFAULTS) { + if (last_color_pos != NULL) + *last_color_pos = (int) (str-start); + if (last_color_len != NULL) + *last_color_len = 2; + } + str += 2; + } else { + if (!IS_COLOR_CODE(*str)) { + if (len-- == 0) + break; + } + str++; + } + } + + return (int) (str-start); +} + +char *strip_codes(const char *input) +{ + const char *p; + char *str, *out; + + out = str = g_strdup(input); + for (p = input; *p != '\0'; p++) { + if (*p == 3) { + p++; + + /* mirc color */ + get_mirc_color(&p, NULL, NULL); + p--; + continue; + } + + if (*p == 4 && p[1] != '\0') { + if (p[1] >= FORMAT_STYLE_SPECIAL) { + p++; + continue; + } + + /* irssi color */ + if (p[2] != '\0') { + p += 2; + continue; + } + } + + if (!IS_COLOR_CODE(*p)) + *out++ = *p; + } + + *out = '\0'; + return str; +} + +/* send a fully parsed text string for GUI to print */ +void format_send_to_gui(TEXT_DEST_REC *dest, const char *text) +{ + char *dup, *str, *ptr, type; + int fgcolor, bgcolor; + int flags; + + dup = str = g_strdup(text); + + flags = 0; fgcolor = -1; bgcolor = -1; + while (*str != '\0') { + type = '\0'; + for (ptr = str; *ptr != '\0'; ptr++) { + if (IS_COLOR_CODE(*ptr) || *ptr == '\n') { + type = *ptr; + *ptr++ = '\0'; + break; + } + + *ptr = (char) translation_in[(int) (unsigned char) *ptr]; + } + + if (type == 7) { + /* bell */ + if (settings_get_bool("bell_beeps")) + signal_emit("beep", 0); + } + + if (*str != '\0') { + /* send the text to gui handler */ + signal_emit_id(signal_gui_print_text, 6, dest->window, + GINT_TO_POINTER(fgcolor), + GINT_TO_POINTER(bgcolor), + GINT_TO_POINTER(flags), str, + dest->level); + flags &= ~PRINTFLAG_INDENT; + } + + if (type == '\n') + format_newline(dest->window); + + if (*ptr == '\0') + break; + + switch (type) + { + case 2: + /* bold */ + if (!hide_text_style) + flags ^= PRINTFLAG_BOLD; + break; + case 3: + /* MIRC color */ + get_mirc_color((const char **) &ptr, + hide_text_style ? NULL : &fgcolor, + hide_text_style ? NULL : &bgcolor); + if (!hide_text_style) + flags |= PRINTFLAG_MIRC_COLOR; + break; + case 4: + /* user specific colors */ + flags &= ~PRINTFLAG_MIRC_COLOR; + switch (*ptr) { + case FORMAT_STYLE_BLINK: + flags ^= PRINTFLAG_BLINK; + break; + case FORMAT_STYLE_UNDERLINE: + flags ^= PRINTFLAG_UNDERLINE; + break; + case FORMAT_STYLE_BOLD: + flags ^= PRINTFLAG_BOLD; + break; + case FORMAT_STYLE_REVERSE: + flags ^= PRINTFLAG_REVERSE; + break; + case FORMAT_STYLE_INDENT: + flags |= PRINTFLAG_INDENT; + break; + case FORMAT_STYLE_DEFAULTS: + fgcolor = bgcolor = -1; + flags &= PRINTFLAG_INDENT; + break; + default: + if (*ptr != FORMAT_COLOR_NOCHANGE) { + fgcolor = (unsigned char) *ptr-'0'; + if (fgcolor <= 7) + flags &= ~PRINTFLAG_BOLD; + else { + /* bold */ + if (fgcolor != 8) fgcolor -= 8; + flags |= PRINTFLAG_BOLD; + } + } + ptr++; + if (*ptr != FORMAT_COLOR_NOCHANGE) + bgcolor = *ptr-'0'; + } + ptr++; + break; + case 6: + /* blink */ + if (!hide_text_style) + flags ^= PRINTFLAG_BLINK; + break; + case 15: + /* remove all styling */ + fgcolor = bgcolor = -1; + flags &= PRINTFLAG_INDENT; + break; + case 22: + /* reverse */ + if (!hide_text_style) + flags ^= PRINTFLAG_REVERSE; + break; + case 31: + /* underline */ + if (!hide_text_style) + flags ^= PRINTFLAG_UNDERLINE; + break; + case 27: + /* ansi color code */ + ptr = get_ansi_color(dest->window == NULL || dest->window->theme == NULL ? + current_theme : dest->window->theme, + ptr, + hide_text_style ? NULL : &fgcolor, + hide_text_style ? NULL : &bgcolor, + hide_text_style ? NULL : &flags); + break; + } + + str = ptr; + } + + g_free(dup); +} + +static void read_settings(void) +{ + hide_server_tags = settings_get_bool("hide_server_tags"); + hide_text_style = settings_get_bool("hide_text_style"); + timestamps = settings_get_bool("timestamps"); + timestamp_timeout = settings_get_int("timestamp_timeout"); + msgs_timestamps = settings_get_bool("msgs_timestamps"); +} + +void formats_init(void) +{ + signal_gui_print_text = signal_get_uniq_id("gui print text"); + + read_settings(); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void formats_deinit(void) +{ + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/fe-common/core/formats.h b/apps/irssi/src/fe-common/core/formats.h new file mode 100644 index 00000000..86c53e69 --- /dev/null +++ b/apps/irssi/src/fe-common/core/formats.h @@ -0,0 +1,115 @@ +#ifndef __FORMATS_H +#define __FORMATS_H + +#include "themes.h" +#include "fe-windows.h" + +#define PRINTFLAG_BOLD 0x01 +#define PRINTFLAG_REVERSE 0x02 +#define PRINTFLAG_UNDERLINE 0x04 +#define PRINTFLAG_BLINK 0x08 +#define PRINTFLAG_MIRC_COLOR 0x10 +#define PRINTFLAG_INDENT 0x20 +#define PRINTFLAG_NEWLINE 0x40 + +#define MAX_FORMAT_PARAMS 10 +#define DEFAULT_FORMAT_ARGLIST_SIZE 200 + +enum { + FORMAT_STRING, + FORMAT_INT, + FORMAT_LONG, + FORMAT_FLOAT +}; + +struct _FORMAT_REC { + char *tag; + char *def; + + int params; + int paramtypes[MAX_FORMAT_PARAMS]; +}; + +typedef struct { + WINDOW_REC *window; + SERVER_REC *server; + const char *target; + int level; + + int hilight_priority; + char *hilight_color; +} TEXT_DEST_REC; + +int format_find_tag(const char *module, const char *tag); + +/* Return length of text part in string (ie. without % codes) */ +int format_get_length(const char *str); +/* Return how many characters in `str' must be skipped before `len' + characters of text is skipped. Like strip_real_length(), except this + handles %codes. */ +int format_real_length(const char *str, int len); + +char *format_string_expand(const char *text); + +char *format_get_text(const char *module, WINDOW_REC *window, + void *server, const char *target, + int formatnum, ...); + +/* good size for buffer is DEFAULT_FORMAT_ARGLIST_SIZE */ +void format_read_arglist(va_list va, FORMAT_REC *format, + char **arglist, int arglist_size, + char *buffer, int buffer_size); +char *format_get_text_theme(THEME_REC *theme, const char *module, + TEXT_DEST_REC *dest, int formatnum, ...); +char *format_get_text_theme_args(THEME_REC *theme, const char *module, + TEXT_DEST_REC *dest, int formatnum, + va_list va); +char *format_get_text_theme_charargs(THEME_REC *theme, const char *module, + TEXT_DEST_REC *dest, int formatnum, + char **args); + +/* add `linestart' to start of each line in `text'. `text' may contain + multiple lines separated with \n. */ +char *format_add_linestart(const char *text, const char *linestart); + +/* return the "-!- " text at the start of the line */ +char *format_get_level_tag(THEME_REC *theme, TEXT_DEST_REC *dest); + +/* return timestamp + server tag */ +char *format_get_line_start(THEME_REC *theme, TEXT_DEST_REC *dest, time_t t); + + +/* "private" functions for printtext */ +void format_create_dest(TEXT_DEST_REC *dest, + void *server, const char *target, + int level, WINDOW_REC *window); + +void format_newline(WINDOW_REC *window); + +/* Return how many characters in `str' must be skipped before `len' + characters of text is skipped. */ +int strip_real_length(const char *str, int len, + int *last_color_pos, int *last_color_len); + +/* strip all color (etc.) codes from `input'. + Returns newly allocated string. */ +char *strip_codes(const char *input); + +/* send a fully parsed text string for GUI to print */ +void format_send_to_gui(TEXT_DEST_REC *dest, const char *text); + +#define FORMAT_COLOR_NOCHANGE ('0'-1) /* don't change this, at least hilighting depends this value */ + +#define FORMAT_STYLE_SPECIAL 0x60 +#define FORMAT_STYLE_BLINK (0x01 + FORMAT_STYLE_SPECIAL) +#define FORMAT_STYLE_UNDERLINE (0x02 + FORMAT_STYLE_SPECIAL) +#define FORMAT_STYLE_BOLD (0x03 + FORMAT_STYLE_SPECIAL) +#define FORMAT_STYLE_REVERSE (0x04 + FORMAT_STYLE_SPECIAL) +#define FORMAT_STYLE_INDENT (0x05 + FORMAT_STYLE_SPECIAL) +#define FORMAT_STYLE_DEFAULTS (0x06 + FORMAT_STYLE_SPECIAL) +int format_expand_styles(GString *out, char format); + +void formats_init(void); +void formats_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/hilight-text.c b/apps/irssi/src/fe-common/core/hilight-text.c new file mode 100644 index 00000000..197957f6 --- /dev/null +++ b/apps/irssi/src/fe-common/core/hilight-text.c @@ -0,0 +1,697 @@ +/* + hilight-text.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "servers.h" +#include "channels.h" +#include "nicklist.h" + +#include "hilight-text.h" +#include "nickmatch-cache.h" +#include "printtext.h" +#include "formats.h" + +static NICKMATCH_REC *nickmatch; +static HILIGHT_REC *next_nick_hilight, *next_line_hilight; +static int next_hilight_start, next_hilight_end; +static int never_hilight_level, default_hilight_level; +GSList *hilights; + +static void reset_cache(void) +{ + GSList *tmp; + + never_hilight_level = MSGLEVEL_ALL & ~default_hilight_level; + for (tmp = hilights; tmp != NULL; tmp = tmp->next) { + HILIGHT_REC *rec = tmp->data; + + if (never_hilight_level & rec->level) + never_hilight_level &= ~rec->level; + } + + nickmatch_rebuild(nickmatch); +} + +static void hilight_add_config(HILIGHT_REC *rec) +{ + CONFIG_NODE *node; + + g_return_if_fail(rec != NULL); + + node = iconfig_node_traverse("(hilights", TRUE); + node = config_node_section(node, NULL, NODE_TYPE_BLOCK); + + iconfig_node_set_str(node, "text", rec->text); + if (rec->level > 0) iconfig_node_set_int(node, "level", rec->level); + if (rec->color) iconfig_node_set_str(node, "color", rec->color); + if (rec->act_color) iconfig_node_set_str(node, "act_color", rec->act_color); + if (rec->priority > 0) iconfig_node_set_int(node, "priority", rec->priority); + iconfig_node_set_bool(node, "nick", rec->nick); + iconfig_node_set_bool(node, "word", rec->word); + if (rec->nickmask) iconfig_node_set_bool(node, "mask", TRUE); + if (rec->fullword) iconfig_node_set_bool(node, "fullword", TRUE); + if (rec->regexp) iconfig_node_set_bool(node, "regexp", TRUE); + + if (rec->channels != NULL && *rec->channels != NULL) { + node = config_node_section(node, "channels", NODE_TYPE_LIST); + iconfig_node_add_list(node, rec->channels); + } +} + +static void hilight_remove_config(HILIGHT_REC *rec) +{ + CONFIG_NODE *node; + + g_return_if_fail(rec != NULL); + + node = iconfig_node_traverse("hilights", FALSE); + if (node != NULL) iconfig_node_list_remove(node, g_slist_index(hilights, rec)); +} + +static void hilight_destroy(HILIGHT_REC *rec) +{ + g_return_if_fail(rec != NULL); + +#ifdef HAVE_REGEX_H + if (rec->regexp_compiled) regfree(&rec->preg); +#endif + if (rec->channels != NULL) g_strfreev(rec->channels); + g_free_not_null(rec->color); + g_free_not_null(rec->act_color); + g_free(rec->text); + g_free(rec); +} + +static void hilights_destroy_all(void) +{ + g_slist_foreach(hilights, (GFunc) hilight_destroy, NULL); + g_slist_free(hilights); + hilights = NULL; +} + +static void hilight_remove(HILIGHT_REC *rec) +{ + g_return_if_fail(rec != NULL); + + hilight_remove_config(rec); + hilights = g_slist_remove(hilights, rec); + hilight_destroy(rec); +} + +static HILIGHT_REC *hilight_find(const char *text, char **channels) +{ + GSList *tmp; + char **chan; + + g_return_val_if_fail(text != NULL, NULL); + + for (tmp = hilights; tmp != NULL; tmp = tmp->next) { + HILIGHT_REC *rec = tmp->data; + + if (g_strcasecmp(rec->text, text) != 0) + continue; + + if ((channels == NULL && rec->channels == NULL)) + return rec; /* no channels - ok */ + + if (channels != NULL && strcmp(*channels, "*") == 0) + return rec; /* ignore channels */ + + if (channels == NULL || rec->channels == NULL) + continue; /* other doesn't have channels */ + + if (strarray_length(channels) != strarray_length(rec->channels)) + continue; /* different amount of channels */ + + /* check that channels match */ + for (chan = channels; *chan != NULL; chan++) { + if (strarray_find(rec->channels, *chan) == -1) + break; + } + + if (*chan == NULL) + return rec; /* channels ok */ + } + + return NULL; +} + +static int hilight_match_text(HILIGHT_REC *rec, const char *text, + int *match_beg, int *match_end) +{ + char *match; + + if (rec->regexp) { +#ifdef HAVE_REGEX_H + regmatch_t rmatch[1]; + + if (rec->regexp_compiled && + regexec(&rec->preg, text, 1, rmatch, 0) == 0) { + if (rmatch[0].rm_so > 0 && + match_beg != NULL && match_end != NULL) { + *match_beg = rmatch[0].rm_so; + *match_end = rmatch[0].rm_eo; + } + return TRUE; + } +#endif + } else { + match = rec->fullword ? + stristr_full(text, rec->text) : + stristr(text, rec->text); + if (match != NULL) { + if (match_beg != NULL && match_end != NULL) { + *match_beg = (int) (match-text); + *match_end = *match_beg + strlen(rec->text); + } + return TRUE; + } + } + + return FALSE; +} + +#define hilight_match_level(rec, level) \ + (level & (((rec)->level != 0 ? rec->level : default_hilight_level))) + +#define hilight_match_channel(rec, channel) \ + ((rec)->channels == NULL || ((channel) != NULL && \ + strarray_find((rec)->channels, (channel)) != -1)) + +HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel, + const char *nick, const char *address, + int level, const char *str, + int *match_beg, int *match_end) +{ + GSList *tmp; + CHANNEL_REC *chanrec; + NICK_REC *nickrec; + + g_return_val_if_fail(str != NULL, NULL); + + if ((never_hilight_level & level) == level) + return NULL; + + if (nick != NULL) { + /* check nick mask hilights */ + chanrec = channel_find(server, channel); + nickrec = chanrec == NULL ? NULL : + nicklist_find(chanrec, nick); + if (nickrec != NULL) { + HILIGHT_REC *rec; + + if (nickrec->host == NULL) + nicklist_set_host(chanrec, nickrec, address); + + rec = nickmatch_find(nickmatch, nickrec); + if (rec != NULL && hilight_match_level(rec, level)) + return rec; + } + } + + for (tmp = hilights; tmp != NULL; tmp = tmp->next) { + HILIGHT_REC *rec = tmp->data; + + if (!rec->nickmask && hilight_match_level(rec, level) && + hilight_match_channel(rec, channel) && + hilight_match_text(rec, str, match_beg, match_end)) + return rec; + } + + return NULL; +} + +static char *hilight_get_act_color(HILIGHT_REC *rec) +{ + g_return_val_if_fail(rec != NULL, NULL); + + return g_strdup(rec->act_color != NULL ? rec->act_color : + rec->color != NULL ? rec->color : + settings_get_str("hilight_act_color")); +} + +static char *hilight_get_color(HILIGHT_REC *rec) +{ + const char *color; + + g_return_val_if_fail(rec != NULL, NULL); + + color = rec->color != NULL ? rec->color : + settings_get_str("hilight_color"); + + return format_string_expand(color); +} + +static void hilight_update_text_dest(TEXT_DEST_REC *dest, HILIGHT_REC *rec) +{ + dest->level |= MSGLEVEL_HILIGHT; + + if (rec->priority > 0) + dest->hilight_priority = rec->priority; + + dest->hilight_color = hilight_get_act_color(rec); +} + +static void sig_print_text_stripped(TEXT_DEST_REC *dest, const char *str) +{ + HILIGHT_REC *hilight; + + g_return_if_fail(str != NULL); + + if (next_nick_hilight != NULL) { + if (!next_nick_hilight->nick) { + /* non-nick hilight wanted */ + hilight = next_nick_hilight; + next_nick_hilight = NULL; + if (!hilight_match_text(hilight, str, + &next_hilight_start, + &next_hilight_end)) { + next_hilight_start = 0; + next_hilight_end = strlen(str); + } + } else { + /* nick is highlighted, just set priority */ + hilight_update_text_dest(dest, next_nick_hilight); + next_nick_hilight = NULL; + return; + } + } else { + if (dest->level & (MSGLEVEL_NOHILIGHT|MSGLEVEL_HILIGHT)) + return; + + hilight = hilight_match(dest->server, dest->target, + NULL, NULL, dest->level, str, + &next_hilight_start, + &next_hilight_end); + } + + if (hilight != NULL) { + /* update the level / hilight info */ + hilight_update_text_dest(dest, hilight); + + next_line_hilight = hilight; + } +} + +static void sig_print_text(TEXT_DEST_REC *dest, const char *str) +{ + char *color, *newstr; + int next_hilight_len; + + if (next_line_hilight == NULL) + return; + + color = hilight_get_color(next_line_hilight); + next_hilight_len = next_hilight_end-next_hilight_start; + + if (!next_line_hilight->word) { + /* hilight whole line */ + char *tmp = strip_codes(str); + newstr = g_strconcat(color, tmp, NULL); + g_free(tmp); + } else { + /* hilight part of the line */ + GString *tmp; + char *middle, *lastcolor; + int pos, color_pos, color_len; + + tmp = g_string_new(NULL); + + /* start of the line */ + pos = strip_real_length(str, next_hilight_start, NULL, NULL); + g_string_append(tmp, str); + g_string_truncate(tmp, pos); + + /* color */ + g_string_append(tmp, color); + + /* middle of the line, stripped */ + middle = strip_codes(str+pos); + pos = tmp->len; + g_string_append(tmp, middle); + g_string_truncate(tmp, pos+next_hilight_len); + g_free(middle); + + /* end of the line */ + pos = strip_real_length(str, next_hilight_end, + &color_pos, &color_len); + if (color_pos > 0) + lastcolor = g_strndup(str+color_pos, color_len); + else { + /* no colors in line, change back to default */ + lastcolor = g_malloc0(3); + lastcolor[0] = 4; + lastcolor[1] = FORMAT_STYLE_DEFAULTS; + } + g_string_append(tmp, lastcolor); + g_string_append(tmp, str+pos); + g_free(lastcolor); + + newstr = tmp->str; + g_string_free(tmp, FALSE); + } + + next_line_hilight = NULL; + signal_emit("print text", 2, dest, newstr); + + g_free(color); + g_free(newstr); + + signal_stop(); +} + +char *hilight_match_nick(SERVER_REC *server, const char *channel, + const char *nick, const char *address, + int level, const char *msg) +{ + HILIGHT_REC *rec; + char *color; + + rec = hilight_match(server, channel, nick, address, + level, msg, NULL, NULL); + color = rec == NULL || !rec->nick ? NULL : + hilight_get_color(rec); + + next_nick_hilight = rec; + return color; +} + +static void read_hilight_config(void) +{ + CONFIG_NODE *node; + HILIGHT_REC *rec; + GSList *tmp; + char *text, *color; + + hilights_destroy_all(); + + node = iconfig_node_traverse("hilights", FALSE); + if (node == NULL) { + reset_cache(); + return; + } + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->type != NODE_TYPE_BLOCK) + continue; + + text = config_node_get_str(node, "text", NULL); + if (text == NULL || *text == '\0') + continue; + + rec = g_new0(HILIGHT_REC, 1); + hilights = g_slist_append(hilights, rec); + + rec->text = g_strdup(text); + + color = config_node_get_str(node, "color", NULL); + rec->color = color == NULL || *color == '\0' ? NULL : + g_strdup(color); + + color = config_node_get_str(node, "act_color", NULL); + rec->act_color = color == NULL || *color == '\0' ? NULL : + g_strdup(color); + + rec->level = config_node_get_int(node, "level", 0); + rec->priority = config_node_get_int(node, "priority", 0); + rec->nick = config_node_get_bool(node, "nick", TRUE); + rec->word = config_node_get_bool(node, "word", TRUE); + + rec->nickmask = config_node_get_bool(node, "mask", FALSE); + rec->fullword = config_node_get_bool(node, "fullword", FALSE); + rec->regexp = config_node_get_bool(node, "regexp", FALSE); + +#ifdef HAVE_REGEX_H + rec->regexp_compiled = !rec->regexp ? FALSE : + regcomp(&rec->preg, rec->text, + REG_EXTENDED|REG_ICASE) == 0; +#endif + + node = config_node_section(node, "channels", -1); + if (node != NULL) rec->channels = config_node_get_list(node); + } + + reset_cache(); +} + +static void hilight_print(int index, HILIGHT_REC *rec) +{ + char *chans, *levelstr; + + chans = rec->channels == NULL ? NULL : + g_strjoinv(",", rec->channels); + levelstr = rec->level == 0 ? NULL : + bits2level(rec->level); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, + TXT_HILIGHT_LINE, index, rec->text, + chans != NULL ? chans : "", + levelstr != NULL ? levelstr : "", + rec->nickmask ? " -mask" : "", + rec->fullword ? " -full" : "", + rec->regexp ? " -regexp" : ""); + g_free_not_null(chans); + g_free_not_null(levelstr); +} + +static void cmd_hilight_show(void) +{ + GSList *tmp; + int index; + + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_HEADER); + index = 1; + for (tmp = hilights; tmp != NULL; tmp = tmp->next, index++) { + HILIGHT_REC *rec = tmp->data; + + hilight_print(index, rec); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_HILIGHT_FOOTER); +} + +/* SYNTAX: HILIGHT [-nick | -word | -line] [-mask | -full | -regexp] + [-color ] [-actcolor ] [-level ] + [-channels ] */ +static void cmd_hilight(const char *data) +{ + GHashTable *optlist; + HILIGHT_REC *rec; + char *colorarg, *actcolorarg, *levelarg, *priorityarg, *chanarg, *text; + char **channels; + void *free_arg; + + g_return_if_fail(data != NULL); + + if (*data == '\0') { + cmd_hilight_show(); + return; + } + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | + PARAM_FLAG_GETREST, "hilight", &optlist, &text)) + return; + + chanarg = g_hash_table_lookup(optlist, "channels"); + levelarg = g_hash_table_lookup(optlist, "level"); + priorityarg = g_hash_table_lookup(optlist, "priority"); + colorarg = g_hash_table_lookup(optlist, "color"); + actcolorarg = g_hash_table_lookup(optlist, "actcolor"); + + if (*text == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + channels = (chanarg == NULL || *chanarg == '\0') ? NULL : + g_strsplit(replace_chars(chanarg, ',', ' '), " ", -1); + + rec = hilight_find(text, channels); + if (rec == NULL) { + rec = g_new0(HILIGHT_REC, 1); + + /* default to nick/word hilighting */ + rec->nick = TRUE; + rec->word = TRUE; + + rec->text = g_strdup(text); + rec->channels = channels; + } else { + g_strfreev(channels); + + hilight_remove_config(rec); + hilights = g_slist_remove(hilights, rec); + } + + rec->level = (levelarg == NULL || *levelarg == '\0') ? 0 : + level2bits(replace_chars(levelarg, ',', ' ')); + rec->priority = priorityarg == NULL ? 0 : atoi(priorityarg); + + if (g_hash_table_lookup(optlist, "line") != NULL) { + rec->word = FALSE; + rec->nick = FALSE; + } + + if (g_hash_table_lookup(optlist, "word") != NULL) { + rec->word = TRUE; + rec->nick = FALSE; + } + + if (g_hash_table_lookup(optlist, "nick") != NULL) + rec->nick = TRUE; + + rec->nickmask = g_hash_table_lookup(optlist, "mask") != NULL; + rec->fullword = g_hash_table_lookup(optlist, "full") != NULL; + rec->regexp = g_hash_table_lookup(optlist, "regexp") != NULL; + + if (colorarg != NULL) { + if (*colorarg != '\0') + rec->color = g_strdup(colorarg); + else + g_free_and_null(rec->color); + } + if (actcolorarg != NULL) { + if (*actcolorarg != '\0') + rec->act_color = g_strdup(actcolorarg); + else + g_free_and_null(rec->act_color); + } + +#ifdef HAVE_REGEX_H + if (rec->regexp_compiled) + regfree(&rec->preg); + rec->regexp_compiled = !rec->regexp ? FALSE : + regcomp(&rec->preg, rec->text, REG_EXTENDED|REG_ICASE) == 0; +#endif + + hilights = g_slist_append(hilights, rec); + hilight_add_config(rec); + + hilight_print(g_slist_index(hilights, rec)+1, rec); + cmd_params_free(free_arg); + + reset_cache(); +} + +/* SYNTAX: DEHILIGHT | */ +static void cmd_dehilight(const char *data) +{ + HILIGHT_REC *rec; + GSList *tmp; + + if (is_numeric(data, ' ')) { + /* with index number */ + tmp = g_slist_nth(hilights, atoi(data)-1); + rec = tmp == NULL ? NULL : tmp->data; + } else { + /* with mask */ + char *chans[2] = { "*", NULL }; + rec = hilight_find(data, chans); + } + + if (rec == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_NOT_FOUND, data); + else { + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_HILIGHT_REMOVED, rec->text); + hilight_remove(rec); + reset_cache(); + } +} + +static void hilight_nick_cache(GHashTable *list, CHANNEL_REC *channel, + NICK_REC *nick) +{ + GSList *tmp; + HILIGHT_REC *match; + char *nickmask; + int len, best_match; + + if (nick->host == NULL) + return; /* don't check until host is known */ + + nickmask = g_strconcat(nick->nick, "!", nick->host, NULL); + + best_match = 0; match = NULL; + for (tmp = hilights; tmp != NULL; tmp = tmp->next) { + HILIGHT_REC *rec = tmp->data; + + if (rec->nickmask && + hilight_match_channel(rec, channel->name) && + match_wildcards(rec->text, nickmask)) { + len = strlen(rec->text); + if (best_match < len) { + best_match = len; + match = rec; + } + } + } + g_free_not_null(nickmask); + + if (match != NULL) + g_hash_table_insert(list, nick, match); +} + +static void read_settings(void) +{ + default_hilight_level = level2bits(settings_get_str("hilight_level")); +} + +void hilight_text_init(void) +{ + settings_add_str("lookandfeel", "hilight_color", "%Y"); + settings_add_str("lookandfeel", "hilight_act_color", "%M"); + settings_add_str("lookandfeel", "hilight_level", "PUBLIC DCCMSGS"); + + next_nick_hilight = NULL; + next_line_hilight = NULL; + + read_settings(); + + nickmatch = nickmatch_init(hilight_nick_cache); + read_hilight_config(); + + signal_add_first("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped); + signal_add_first("print text", (SIGNAL_FUNC) sig_print_text); + signal_add("setup reread", (SIGNAL_FUNC) read_hilight_config); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + command_bind("hilight", NULL, (SIGNAL_FUNC) cmd_hilight); + command_bind("dehilight", NULL, (SIGNAL_FUNC) cmd_dehilight); + command_set_options("hilight", "-color -actcolor -level -priority -channels nick word line mask full regexp"); +} + +void hilight_text_deinit(void) +{ + hilights_destroy_all(); + nickmatch_deinit(nickmatch); + + signal_remove("print text stripped", (SIGNAL_FUNC) sig_print_text_stripped); + signal_remove("print text", (SIGNAL_FUNC) sig_print_text); + signal_remove("setup reread", (SIGNAL_FUNC) read_hilight_config); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + + command_unbind("hilight", (SIGNAL_FUNC) cmd_hilight); + command_unbind("dehilight", (SIGNAL_FUNC) cmd_dehilight); +} diff --git a/apps/irssi/src/fe-common/core/hilight-text.h b/apps/irssi/src/fe-common/core/hilight-text.h new file mode 100644 index 00000000..92093bb1 --- /dev/null +++ b/apps/irssi/src/fe-common/core/hilight-text.h @@ -0,0 +1,44 @@ +#ifndef __HILIGHT_TEXT_H +#define __HILIGHT_TEXT_H + +#ifdef HAVE_REGEX_H +# include +#endif + +typedef struct { + char *text; + + char **channels; /* if non-NULL, check the text only from these channels */ + int level; /* match only messages with this level, 0=default */ + char *color; /* if starts with number, \003 is automatically + inserted before it. */ + char *act_color; /* color for window activity */ + int priority; + + unsigned int nick:1; /* hilight only nick if possible */ + unsigned int word:1; /* hilight only word, not full line */ + + unsigned int nickmask:1; /* `text' is a nick mask */ + unsigned int fullword:1; /* match `text' only for full words */ + unsigned int regexp:1; /* `text' is a regular expression */ +#ifdef HAVE_REGEX_H + unsigned int regexp_compiled:1; /* should always be TRUE, unless regexp is invalid */ + regex_t preg; +#endif +} HILIGHT_REC; + +extern GSList *hilights; + +HILIGHT_REC *hilight_match(SERVER_REC *server, const char *channel, + const char *nick, const char *address, + int level, const char *str, + int *match_beg, int *match_end); + +char *hilight_match_nick(SERVER_REC *server, const char *channel, + const char *nick, const char *address, + int level, const char *msg); + +void hilight_text_init(void); +void hilight_text_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/keyboard.c b/apps/irssi/src/fe-common/core/keyboard.c new file mode 100644 index 00000000..0b7ac714 --- /dev/null +++ b/apps/irssi/src/fe-common/core/keyboard.c @@ -0,0 +1,820 @@ +/* + keyboard.c : irssi + + Copyright (C) 1999-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "keyboard.h" +#include "fe-windows.h" +#include "printtext.h" + +GSList *keyinfos; +static GHashTable *keys, *default_keys; + +/* A cache of some sort for key presses that generate a single char only. + If the key isn't used, used_keys[key] is zero. */ +static char used_keys[256]; + +/* contains list of all key bindings of which command is "key" - + this can be used to check fast if some command queue exists or not. + Format is _always_ in key1-key2-key3 format (like ^W-^N, + not ^W^N) */ +static GTree *key_states; +/* List of all key combo names */ +static GSList *key_combos; +static int key_config_frozen; + +struct KEYBOARD_REC { + /* example: + /BIND ^[ key meta + /BIND meta-O key meta2 + /BIND meta-[ key meta2 + + /BIND meta2-C key right + /BIND ^W-meta-right /echo ^W Meta-right key pressed + + When ^W Meta-Right is pressed, the full char combination + is "^W^[^[[C". + + We'll get there with key states: + ^W - key_prev_state = NULL, key_state = NULL -> ^W + ^[ - key_prev_state = NULL, key_state = ^W -> meta + ^[ - key_prev_state = ^W, key_state = meta -> meta + [ - key_prev_state = ^W-meta, key_state = meta -> meta2 + C - key_prev_state = ^W-meta, key_state = meta2 -> right + key_prev_state = ^W-meta, key_state = right -> ^W-meta-right + + key_state is moved to key_prev_state if there's nothing else in + /BINDs matching for key_state-newkey. + + ^X^Y equals to ^X-^Y, ABC equals to A-B-C unless there's ABC + named key. ^ can be used with ^^ and - with -- */ + char *key_state, *key_prev_state; + + /* GUI specific data sent in "key pressed" signal */ + void *gui_data; +}; + +/* Creates a new "keyboard" - this is used only for keeping track of + key combo states and sending the gui_data parameter in "key pressed" + signal */ +KEYBOARD_REC *keyboard_create(void *data) +{ + KEYBOARD_REC *rec; + + rec = g_new0(KEYBOARD_REC, 1); + rec->gui_data = data; + + signal_emit("keyboard created", 1, rec); + return rec; +} + +/* Destroys a keyboard */ +void keyboard_destroy(KEYBOARD_REC *keyboard) +{ + signal_emit("keyboard destroyed", 1, keyboard); + + g_free_not_null(keyboard->key_state); + g_free_not_null(keyboard->key_prev_state); + g_free(keyboard); +} + +static void key_destroy(KEY_REC *rec, GHashTable *hash) +{ + g_hash_table_remove(hash, rec->key); + + g_free_not_null(rec->data); + g_free(rec->key); + g_free(rec); +} + +static void key_default_add(const char *id, const char *key, const char *data) +{ + KEYINFO_REC *info; + KEY_REC *rec; + + info = key_info_find(id); + if (info == NULL) + return; + + rec = g_hash_table_lookup(default_keys, key); + if (rec != NULL) { + /* key already exists, replace */ + rec->info->default_keys = + g_slist_remove(rec->info->default_keys, rec); + key_destroy(rec, default_keys); + } + + rec = g_new0(KEY_REC, 1); + rec->key = g_strdup(key); + rec->info = info; + rec->data = g_strdup(data); + info->default_keys = g_slist_append(info->default_keys, rec); + g_hash_table_insert(default_keys, rec->key, rec); +} + +static CONFIG_NODE *key_config_find(const char *key) +{ + CONFIG_NODE *node; + GSList *tmp; + + /* remove old keyboard settings */ + node = iconfig_node_traverse("(keyboard", TRUE); + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (strcmp(config_node_get_str(node, "key", ""), key) == 0) + return node; + } + + return NULL; +} + +static void keyconfig_save(const char *id, const char *key, const char *data) +{ + CONFIG_NODE *node; + + g_return_if_fail(id != NULL); + g_return_if_fail(key != NULL); + + node = key_config_find(key); + if (node == NULL) { + node = iconfig_node_traverse("(keyboard", TRUE); + node = config_node_section(node, NULL, NODE_TYPE_BLOCK); + } + + iconfig_node_set_str(node, "key", key); + iconfig_node_set_str(node, "id", id); + iconfig_node_set_str(node, "data", data); +} + +static void keyconfig_clear(const char *key) +{ + CONFIG_NODE *node; + + g_return_if_fail(key != NULL); + + /* remove old keyboard settings */ + node = key_config_find(key); + if (node != NULL) + iconfig_node_clear(node); +} + +KEYINFO_REC *key_info_find(const char *id) +{ + GSList *tmp; + + for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) { + KEYINFO_REC *rec = tmp->data; + + if (g_strcasecmp(rec->id, id) == 0) + return rec; + } + + return NULL; +} + +static KEY_REC *key_combo_find(const char *key) +{ + KEYINFO_REC *info; + GSList *tmp; + + info = key_info_find("key"); + if (info == NULL) + return NULL; + + for (tmp = info->keys; tmp != NULL; tmp = tmp->next) { + KEY_REC *rec = tmp->data; + + if (strcmp(rec->data, key) == 0) + return rec; + } + + return NULL; +} + +static void key_states_scan_key(const char *key, KEY_REC *rec, GString *temp) +{ + char **keys, **tmp, *p; + + g_string_truncate(temp, 0); + + /* meta-^W^Gfoo -> meta-^W-^G-f-o-o */ + keys = g_strsplit(key, "-", -1); + for (tmp = keys; *tmp != NULL; tmp++) { + if (key_combo_find(*tmp)) { + /* key combo */ + g_string_append(temp, *tmp); + g_string_append_c(temp, '-'); + continue; + } + + if (**tmp == '\0') { + /* '-' */ + g_string_append(temp, "--"); + continue; + } + + for (p = *tmp; *p != '\0'; p++) { + g_string_append_c(temp, *p); + + if (*p == '^') { + /* ctrl-code */ + if (p[1] != '\0') + p++; + g_string_append_c(temp, *p); + } + + g_string_append_c(temp, '-'); + } + } + g_strfreev(keys); + + if (temp->len > 0) { + g_string_truncate(temp, temp->len-1); + + if (temp->str[1] == '-' || temp->str[1] == '\0') + used_keys[(int) (unsigned char) temp->str[0]] = 1; + g_tree_insert(key_states, g_strdup(temp->str), rec); + } +} + +static int key_state_destroy(char *key) +{ + g_free(key); + return FALSE; +} + +/* Rescan all the key combos and figure out which characters are supposed + to be treated as characters and which as key combos. + Yes, this is pretty slow function... */ +static void key_states_rescan(void) +{ + GString *temp; + + memset(used_keys, 0, sizeof(used_keys)); + + g_tree_traverse(key_states, (GTraverseFunc) key_state_destroy, + G_IN_ORDER, NULL); + g_tree_destroy(key_states); + key_states = g_tree_new((GCompareFunc) strcmp); + + temp = g_string_new(NULL); + g_hash_table_foreach(keys, (GHFunc) key_states_scan_key, temp); + g_string_free(temp, TRUE); +} + +void key_configure_freeze(void) +{ + key_config_frozen++; +} + +void key_configure_thaw(void) +{ + g_return_if_fail(key_config_frozen > 0); + + if (--key_config_frozen == 0) + key_states_rescan(); +} + +static void key_configure_destroy(KEY_REC *rec) +{ + g_return_if_fail(rec != NULL); + + rec->info->keys = g_slist_remove(rec->info->keys, rec); + g_hash_table_remove(keys, rec->key); + + if (!key_config_frozen) + key_states_rescan(); + + g_free_not_null(rec->data); + g_free(rec->key); + g_free(rec); +} + +/* Configure new key */ +static void key_configure_create(const char *id, const char *key, + const char *data) +{ + KEYINFO_REC *info; + KEY_REC *rec; + + g_return_if_fail(id != NULL); + g_return_if_fail(key != NULL && *key != '\0'); + + info = key_info_find(id); + if (info == NULL) + return; + + rec = g_hash_table_lookup(keys, key); + if (rec != NULL) + key_configure_destroy(rec); + + rec = g_new0(KEY_REC, 1); + rec->key = g_strdup(key); + rec->info = info; + rec->data = g_strdup(data); + info->keys = g_slist_append(info->keys, rec); + g_hash_table_insert(keys, rec->key, rec); + + if (!key_config_frozen) + key_states_rescan(); +} + +/* Bind a key for function */ +void key_bind(const char *id, const char *description, + const char *key_default, const char *data, SIGNAL_FUNC func) +{ + KEYINFO_REC *info; + char *key; + + g_return_if_fail(id != NULL); + + /* create key info record */ + info = key_info_find(id); + if (info == NULL) { + g_return_if_fail(func != NULL); + + if (description == NULL) + g_warning("key_bind(%s) should have description!", id); + info = g_new0(KEYINFO_REC, 1); + info->id = g_strdup(id); + info->description = g_strdup(description); + keyinfos = g_slist_append(keyinfos, info); + + /* add the signal */ + key = g_strconcat("key ", id, NULL); + signal_add(key, func); + g_free(key); + + signal_emit("keyinfo created", 1, info); + } + + if (key_default != NULL && *key_default != '\0') { + key_default_add(id, key_default, data); + key_configure_create(id, key_default, data); + } +} + +static void keyinfo_remove(KEYINFO_REC *info) +{ + g_return_if_fail(info != NULL); + + keyinfos = g_slist_remove(keyinfos, info); + signal_emit("keyinfo destroyed", 1, info); + + /* destroy all keys */ + g_slist_foreach(info->keys, (GFunc) key_destroy, keys); + g_slist_foreach(info->default_keys, (GFunc) key_destroy, default_keys); + + /* destroy key info */ + g_slist_free(info->keys); + g_slist_free(info->default_keys); + g_free_not_null(info->description); + g_free(info->id); + g_free(info); +} + +/* Unbind key */ +void key_unbind(const char *id, SIGNAL_FUNC func) +{ + KEYINFO_REC *info; + char *key; + + g_return_if_fail(id != NULL); + g_return_if_fail(func != NULL); + + /* remove keys */ + info = key_info_find(id); + if (info != NULL) + keyinfo_remove(info); + + /* remove signal */ + key = g_strconcat("key ", id, NULL); + signal_remove(key, func); + g_free(key); +} + +/* Configure new key */ +void key_configure_add(const char *id, const char *key, const char *data) +{ + g_return_if_fail(id != NULL); + g_return_if_fail(key != NULL && *key != '\0'); + + key_configure_create(id, key, data); + keyconfig_save(id, key, data); +} + +/* Remove key */ +void key_configure_remove(const char *key) +{ + KEY_REC *rec; + + g_return_if_fail(key != NULL); + + rec = g_hash_table_lookup(keys, key); + if (rec == NULL) return; + + keyconfig_clear(key); + key_configure_destroy(rec); +} + +static int key_emit_signal(KEYBOARD_REC *keyboard, KEY_REC *key) +{ + int consumed; + char *str; + + str = g_strconcat("key ", key->info->id, NULL); + consumed = signal_emit(str, 3, key->data, keyboard->gui_data, key->info); + g_free(str); + + return consumed; +} + +int key_states_search(const char *combo, const char *search) +{ + while (*search != '\0') { + if (*combo != *search) + return *search - *combo; + search++; combo++; + } + + return *combo == '\0' || *combo == '-' ? 0 : -1; +} + +/* Returns TRUE if key press was consumed. Control characters should be sent + as "^@" .. "^_" instead of #0..#31 chars, #127 should be sent as ^? */ +int key_pressed(KEYBOARD_REC *keyboard, const char *key) +{ + KEY_REC *rec; + char *str; + int consumed; + + g_return_val_if_fail(keyboard != NULL, FALSE); + g_return_val_if_fail(key != NULL && *key != '\0', FALSE); + + if (keyboard->key_state == NULL) { + if (key[1] == '\0' && + !used_keys[(int) (unsigned char) key[0]]) { + /* fast check - key not used */ + return FALSE; + } + + rec = g_tree_search(key_states, + (GSearchFunc) key_states_search, + (void *) key); + if (rec == NULL || + (g_tree_lookup(key_states, (void *) key) != NULL && + strcmp(rec->info->id, "key") != 0)) { + /* a single non-combo key was pressed */ + rec = g_hash_table_lookup(keys, key); + if (rec == NULL) + return FALSE; + consumed = key_emit_signal(keyboard, rec); + + /* never consume non-control characters */ + return consumed && key[1] != '\0'; + } + } + + if (keyboard->key_state == NULL) { + /* first key in combo */ + rec = g_tree_lookup(key_states, (void *) key); + } else { + /* continuing key combination */ + str = g_strconcat(keyboard->key_state, "-", key, NULL); + rec = g_tree_lookup(key_states, str); + g_free(str); + } + + if (rec != NULL && strcmp(rec->info->id, "key") == 0) { + /* combo has a specified name, use it */ + g_free_not_null(keyboard->key_state); + keyboard->key_state = g_strdup(rec->data); + } else { + /* some unnamed key - move key_state after key_prev_state + and replace key_state with this new key */ + if (keyboard->key_prev_state == NULL) + keyboard->key_prev_state = keyboard->key_state; + else { + str = g_strconcat(keyboard->key_prev_state, "-", + keyboard->key_state, NULL); + g_free(keyboard->key_prev_state); + g_free(keyboard->key_state); + keyboard->key_prev_state = str; + } + + keyboard->key_state = g_strdup(key); + } + + /* what to do with the key combo? */ + str = keyboard->key_prev_state == NULL ? + g_strdup(keyboard->key_state) : + g_strconcat(keyboard->key_prev_state, "-", + keyboard->key_state, NULL); + + rec = g_tree_lookup(key_states, str); + if (rec != NULL) { + if (strcmp(rec->info->id, "key") == 0) + rec = g_tree_lookup(key_states, rec->data); + + if (rec != NULL) { + /* full key combo */ + key_emit_signal(keyboard, rec); + rec = NULL; + } + } else { + /* check that combo is possible */ + rec = g_tree_search(key_states, + (GSearchFunc) key_states_search, str); + } + + if (rec == NULL) { + /* a) key combo finished, b) unknown key combo, abort */ + g_free_and_null(keyboard->key_prev_state); + g_free_and_null(keyboard->key_state); + } + + g_free(str); + return TRUE; +} + +void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry, + int flags, void *data) +{ + signal_emit("gui entry redirect", 4, func, entry, + GINT_TO_POINTER(flags), data); +} + +static void sig_command(const char *data) +{ + const char *cmdchars; + char *str; + + cmdchars = settings_get_str("cmdchars"); + str = strchr(cmdchars, *data) != NULL ? g_strdup(data) : + g_strdup_printf("%c%s", *cmdchars, data); + + signal_emit("send command", 3, str, active_win->active_server, active_win->active); + + g_free(str); +} + +static void sig_key(const char *data) +{ + /* we should never get here */ +} + +static void sig_multi(const char *data, void *gui_data) +{ + KEYINFO_REC *info; + char **list, **tmp, *p, *str; + + list = g_strsplit(data, ";", -1); + for (tmp = list; *tmp != NULL; tmp++) { + p = strchr(*tmp, ' '); + if (p != NULL) *p++ = '\0'; else p = ""; + + info = key_info_find(*tmp); + if (info != NULL) { + str = g_strconcat("key ", info->id, NULL); + signal_emit(str, 3, p, gui_data, info); + g_free(str); + } + } + g_strfreev(list); +} + +static void cmd_show_keys(const char *searchkey, int full) +{ + GSList *info, *key; + int len; + + len = searchkey == NULL ? 0 : strlen(searchkey); + for (info = keyinfos; info != NULL; info = info->next) { + KEYINFO_REC *rec = info->data; + + for (key = rec->keys; key != NULL; key = key->next) { + KEY_REC *rec = key->data; + + if ((len == 0 || g_strncasecmp(rec->key, searchkey, len) == 0) && + (!full || rec->key[len] == '\0')) { + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_BIND_KEY, + rec->key, rec->info->id, rec->data == NULL ? "" : rec->data); + } + } + } +} + +/* SYNTAX: BIND [-delete] [ [ []]] */ +static void cmd_bind(const char *data) +{ + GHashTable *optlist; + char *key, *id, *keydata; + void *free_arg; + int command_id; + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS, + "bind", &optlist, &key, &id, &keydata)) + return; + + if (*key != '\0' && g_hash_table_lookup(optlist, "delete")) { + /* delete key */ + key_configure_remove(key); + cmd_params_free(free_arg); + return; + } + + if (*id == '\0') { + /* show some/all keys */ + cmd_show_keys(key, FALSE); + cmd_params_free(free_arg); + return; + } + + command_id = strchr(settings_get_str("cmdchars"), *id) != NULL; + if (command_id) { + /* using shortcut to command id */ + keydata = g_strconcat(id, " ", keydata, NULL); + id = "command"; + } + + if (key_info_find(id) == NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_BIND_UNKNOWN_ID, id); + else { + key_configure_add(id, key, keydata); + cmd_show_keys(key, TRUE); + } + + if (command_id) g_free(keydata); + cmd_params_free(free_arg); +} + +static GList *completion_get_keyinfos(const char *info) +{ + GList *list; + GSList *tmp; + int len; + + list = NULL; len = strlen(info); + for (tmp = keyinfos; tmp != NULL; tmp = tmp->next) { + KEYINFO_REC *rec = tmp->data; + + if (g_strncasecmp(rec->id, info, len) == 0) + list = g_list_append(list, g_strdup(rec->id)); + } + + return list; +} + +static void sig_complete_bind(GList **list, WINDOW_REC *window, + const char *word, const char *line, + int *want_space) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(line != NULL); + + if (*line == '\0' || strchr(line, ' ') != NULL) + return; + + *list = completion_get_keyinfos(word); + if (*list != NULL) signal_stop(); +} + +static int key_destroy_hash(const char *key, KEY_REC *rec) +{ + rec->info->keys = g_slist_remove(rec->info->keys, rec); + + g_free_not_null(rec->data); + g_free(rec->key); + g_free(rec); + return TRUE; +} + +static void key_copy_default(const char *key, KEY_REC *orig) +{ + KEY_REC *rec; + + rec = g_new0(KEY_REC, 1); + rec->key = g_strdup(orig->key); + rec->info = orig->info; + rec->data = g_strdup(orig->data); + + rec->info->keys = g_slist_append(rec->info->keys, rec); + g_hash_table_insert(keys, rec->key, rec); +} + +static void keyboard_reset_defaults(void) +{ + g_hash_table_foreach_remove(keys, (GHRFunc) key_destroy_hash, NULL); + g_hash_table_foreach(default_keys, (GHFunc) key_copy_default, NULL); +} + +static void key_config_read(CONFIG_NODE *node) +{ + char *key, *id, *data; + + g_return_if_fail(node != NULL); + + key = config_node_get_str(node, "key", NULL); + id = config_node_get_str(node, "id", NULL); + data = config_node_get_str(node, "data", NULL); + + if (key != NULL && id != NULL) + key_configure_create(id, key, data); +} + +static void read_keyboard_config(void) +{ + CONFIG_NODE *node; + GSList *tmp; + + key_configure_freeze(); + + keyboard_reset_defaults(); + + node = iconfig_node_traverse("keyboard", FALSE); + if (node == NULL) { + key_configure_thaw(); + return; + } + + /* FIXME: backward "compatibility" - remove after irssi .99 */ + if (node->type != NODE_TYPE_LIST) { + iconfig_node_remove(NULL, node); + key_configure_thaw(); + return; + } + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) + key_config_read(tmp->data); + + key_configure_thaw(); +} + +void keyboard_init(void) +{ + keys = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + default_keys = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + keyinfos = NULL; + key_states = g_tree_new((GCompareFunc) strcmp); + key_combos = NULL; + key_config_frozen = 0; + memset(used_keys, 0, sizeof(used_keys)); + + key_bind("command", "Run any IRC command", NULL, NULL, (SIGNAL_FUNC) sig_command); + key_bind("key", "Specify name for key binding", NULL, NULL, (SIGNAL_FUNC) sig_key); + key_bind("multi", "Run multiple commands", NULL, NULL, (SIGNAL_FUNC) sig_multi); + + /* read the keyboard config when all key binds are known */ + signal_add("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config); + signal_add("setup reread", (SIGNAL_FUNC) read_keyboard_config); + signal_add("complete command bind", (SIGNAL_FUNC) sig_complete_bind); + + command_bind("bind", NULL, (SIGNAL_FUNC) cmd_bind); + command_set_options("bind", "delete"); +} + +void keyboard_deinit(void) +{ + while (keyinfos != NULL) + keyinfo_remove(keyinfos->data); + g_hash_table_destroy(keys); + g_hash_table_destroy(default_keys); + + g_tree_traverse(key_states, (GTraverseFunc) key_state_destroy, + G_IN_ORDER, NULL); + g_tree_destroy(key_states); + + signal_remove("irssi init read settings", (SIGNAL_FUNC) read_keyboard_config); + signal_remove("setup reread", (SIGNAL_FUNC) read_keyboard_config); + signal_remove("complete command bind", (SIGNAL_FUNC) sig_complete_bind); + command_unbind("bind", (SIGNAL_FUNC) cmd_bind); +} diff --git a/apps/irssi/src/fe-common/core/keyboard.h b/apps/irssi/src/fe-common/core/keyboard.h new file mode 100644 index 00000000..9f2652e0 --- /dev/null +++ b/apps/irssi/src/fe-common/core/keyboard.h @@ -0,0 +1,55 @@ +#ifndef __KEYBOARD_H +#define __KEYBOARD_H + +#include "signals.h" + +typedef struct KEYBOARD_REC KEYBOARD_REC; + +typedef struct { + char *id; + char *description; + + GSList *keys, *default_keys; +} KEYINFO_REC; + +typedef struct { + KEYINFO_REC *info; + + char *key; + char *data; +} KEY_REC; + +extern GSList *keyinfos; + +/* Creates a new "keyboard" - this is used only for keeping track of + key combo states and sending the gui_data parameter in "key pressed" + signal */ +KEYBOARD_REC *keyboard_create(void *gui_data); +/* Destroys a keyboard */ +void keyboard_destroy(KEYBOARD_REC *keyboard); +/* Returns TRUE if key press was consumed. Control characters should be sent + as "^@" .. "^_" instead of #0..#31 chars, #127 should be sent as ^? */ +int key_pressed(KEYBOARD_REC *keyboard, const char *key); + +void key_bind(const char *id, const char *description, + const char *key_default, const char *data, SIGNAL_FUNC func); +void key_unbind(const char *id, SIGNAL_FUNC func); + +void key_configure_freeze(void); +void key_configure_thaw(void); + +void key_configure_add(const char *id, const char *key, const char *data); +void key_configure_remove(const char *key); + +KEYINFO_REC *key_info_find(const char *id); + +#define ENTRY_REDIRECT_FLAG_HOTKEY 0x01 +#define ENTRY_REDIRECT_FLAG_HIDDEN 0x02 + +void keyboard_entry_redirect(SIGNAL_FUNC func, const char *entry, + int flags, void *data); + +void keyboard_init(void); +void keyboard_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/module-formats.c b/apps/irssi/src/fe-common/core/module-formats.c new file mode 100644 index 00000000..0f37ccfd --- /dev/null +++ b/apps/irssi/src/fe-common/core/module-formats.c @@ -0,0 +1,228 @@ +/* + module-formats.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "formats.h" + +FORMAT_REC fecommon_core_formats[] = { + { MODULE_NAME, "Core", 0 }, + + /* ---- */ + { NULL, "Windows", 0 }, + + { "line_start", "{line_start}", 0 }, + { "line_start_irssi", "{line_start}{hilight Irssi:} ", 0 }, + { "timestamp", "{timestamp $Z} ", 6, { 1, 1, 1, 1, 1, 1 } }, + { "servertag", "[$0] ", 1, { 0 } }, + { "daychange", "Day changed to $[-2.0]{0} $3 $2", 4, { 1, 1, 1, 0 } }, + { "talking_with", "You are now talking with {nick $0}", 1, { 0 } }, + { "refnum_too_low", "Window number must be greater than 1", 0 }, + { "error_server_sticky", "Window's server is sticky and it cannot be changed without -unsticky option", 0 }, + { "set_server_sticky", "Window's server set sticky", 1, { 0 } }, + { "unset_server_sticky", "Window's server isn't sticky anymore", 1, { 0 } }, + { "window_level", "Window level is now $0", 1, { 0 } }, + { "windowlist_header", "Ref Name Active item Server Level", 0 }, + { "windowlist_line", "$[3]0 %|$[20]1 $[15]2 $[15]3 $4", 5, { 1, 0, 0, 0, 0 } }, + { "windowlist_footer", "", 0 }, + { "windows_layout_saved", "Layout of windows is now remembered next time you start silc", 0 }, + { "windows_layout_reset", "Layout of windows reset to defaults", 0 }, + + /* ---- */ + { NULL, "Server", 0 }, + + { "looking_up", "Looking up {server $0}", 1, { 0 } }, + { "connecting", "Connecting to {server $0} [$1] port {hilight $2}", 3, { 0, 0, 1 } }, + { "connection_established", "Connection to {server $0} established", 1, { 0 } }, + { "cant_connect", "Unable to connect server {server $0} port {hilight $1} {reason $2}", 3, { 0, 1, 0 } }, + { "connection_lost", "Connection lost to {server $0}", 1, { 0 } }, + { "lag_disconnected", "No PONG reply from server {server $0} in $1 seconds, disconnecting", 2, { 0, 1 } }, + { "disconnected", "Disconnected from {server $0} {reason $1}", 2, { 0, 0 } }, + { "server_quit", "Disconnecting from server {server $0}: {reason $1}", 2, { 0, 0 } }, + { "server_changed", "Changed to {hilight $2} server {server $1}", 3, { 0, 0, 0 } }, + { "unknown_server_tag", "Unknown server tag {server $0}", 1, { 0 } }, + { "no_connected_servers", "Not connected to any servers", 0 }, + { "server_list", "{server $0}: $1:$2 ($3)", 5, { 0, 0, 1, 0, 0 } }, + { "server_lookup_list", "{server $0}: $1:$2 ($3) (connecting...)", 5, { 0, 0, 1, 0, 0 } }, + { "server_reconnect_list", "{server $0}: $1:$2 ($3) ($5 left before reconnecting)", 6, { 0, 0, 1, 0, 0, 0 } }, + { "server_reconnect_removed", "Removed reconnection to server {server $0} port {hilight $1}", 3, { 0, 1, 0 } }, + { "server_reconnect_not_found", "Reconnection tag {server $0} not found", 1, { 0 } }, + { "setupserver_added", "Server {server $0} saved", 2, { 0, 1 } }, + { "setupserver_removed", "Server {server $0} removed", 2, { 0, 1 } }, + { "setupserver_not_found", "Server {server $0} not found", 2, { 0, 1 } }, + + /* ---- */ + { NULL, "Channels", 0 }, + + { "join", "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}", 3, { 0, 0, 0 } }, + { "part", "{channick $0} {chanhost $1} has left {channel $2} {reason $3}", 4, { 0, 0, 0, 0 } }, + { "kick", "{channick $0} was kicked from {channel $1} by {nick $2} {reason $3}", 4, { 0, 0, 0, 0 } }, + { "quit", "{channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } }, + { "quit_once", "{channel $3} {channick $0} {chanhost $1} has quit {reason $2}", 4, { 0, 0, 0, 0 } }, + { "invite", "{nick $0} invites you to {channel $1}", 2, { 0, 0 } }, + { "new_topic", "{nick $0} changed the topic of {channel $1} to: $2", 3, { 0, 0, 0 } }, + { "topic_unset", "Topic unset by {nick $0} on {channel $1}", 2, { 0, 0 } }, + { "your_nick_changed", "You're now known as {nick $1}", 3, { 0, 0, 0 } }, + { "nick_changed", "{channick $0} is now known as {channick_hilight $1}", 3, { 0, 0, 0 } }, + { "talking_in", "You are now talking in {channel $0}", 1, { 0 } }, + { "not_in_channels", "You are not on any channels", 0 }, + { "current_channel", "Current channel {channel $0}", 1, { 0 } }, + { "names", "{names_users Users {names_channel $0}} $1", 2, { 0, 0 } }, + { "names_nick", "{names_nick $0 $1}", 2, { 0, 0 } }, + { "endofnames", "{channel $0}: Total of {hilight $1} nicks {comment {hilight $2} ops, {hilight $3} voices, {hilight $4} normal}", 5, { 0, 1, 1, 1, 1 } }, + { "chanlist_header", "You are on the following channels:", 0 }, + { "chanlist_line", "{channel $[-10]0} %|+$1 ($2): $3", 4, { 0, 0, 0, 0 } }, + { "chansetup_not_found", "Channel {channel $0} not found", 2, { 0, 0 } }, + { "chansetup_added", "Channel {channel $0} saved", 2, { 0, 0 } }, + { "chansetup_removed", "Channel {channel $0} removed", 2, { 0, 0 } }, + { "chansetup_header", "Channel IRC net Password Settings", 0 }, + { "chansetup_line", "{channel $[15]0} %|$[10]1 $[10]2 $3", 4, { 0, 0, 0, 0 } }, + { "chansetup_footer", "", 0 }, + { "channel_move_notify", "{channel $0} is already joined in window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window", 2, { 0, 1 } }, + + /* ---- */ + { NULL, "Messages", 0 }, + + { "own_msg", "{ownmsgnick $2 {ownnick $0}}$1", 3, { 0, 0, 0 } }, + { "own_msg_channel", "{ownmsgnick $3 {ownnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } }, + { "own_msg_private", "{ownprivmsg msg $0}$1", 2, { 0, 0 } }, + { "own_msg_private_query", "{ownprivmsgnick {ownprivnick $2}}$1", 3, { 0, 0, 0 } }, + { "pubmsg_me", "{pubmsgmenick $2 {menick $0}}$1", 3, { 0, 0, 0 } }, + { "pubmsg_me_channel", "{pubmsgmenick $3 {menick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } }, + { "pubmsg_hilight", "{pubmsghinick $0 $3 $1}$2", 4, { 0, 0, 0, 0 } }, + { "pubmsg_hilight_channel", "{pubmsghinick $0 $4 $1{msgchannel $2}}$3", 5, { 0, 0, 0, 0, 0 } }, + { "pubmsg", "{pubmsgnick $2 {pubnick $0}}$1", 3, { 0, 0, 0 } }, + { "pubmsg_channel", "{pubmsgnick $3 {pubnick $0}{msgchannel $1}}$2", 4, { 0, 0, 0, 0 } }, + { "msg_private", "{privmsg $0 $1}$2", 3, { 0, 0, 0 } }, + { "msg_private_query", "{privmsgnick $0}$2", 3, { 0, 0, 0 } }, + { "no_msgs_got", "You have not received a message from anyone yet", 0 }, + { "no_msgs_sent", "You have not sent a message to anyone yet", 0 }, + + /* ---- */ + { NULL, "Queries", 0 }, + + { "query_start", "Starting query with {nick $0}", 1, { 0 } }, + { "no_query", "No query with {nick $0}", 1, { 0 } }, + { "query_server_changed", "Query with {nick $0} changed to server {server $1}", 2, { 0, 0 } }, + { "query_move_notify", "Query with {nick $0} is already created to window $1, use \"/WINDOW ITEM MOVE $0\" to move it to this window", 2, { 0, 1 } }, + + /* ---- */ + { NULL, "Highlighting", 0 }, + + { "hilight_header", "Highlights:", 0 }, + { "hilight_line", "$[-4]0 $1 $2 $3$4$5", 7, { 1, 0, 0, 0, 0, 0, 0 } }, + { "hilight_footer", "", 0 }, + { "hilight_not_found", "Highlight not found: $0", 1, { 0 } }, + { "hilight_removed", "Highlight removed: $0", 1, { 0 } }, + + /* ---- */ + { NULL, "Aliases", 0 }, + + { "alias_added", "Alias $0 added", 1, { 0 } }, + { "alias_removed", "Alias $0 removed", 1, { 0 } }, + { "alias_not_found", "No such alias: $0", 1, { 0 } }, + { "aliaslist_header", "Aliases:", 0 }, + { "aliaslist_line", "$[10]0 $1", 2, { 0, 0 } }, + { "aliaslist_footer", "", 0 }, + + /* ---- */ + { NULL, "Logging", 0 }, + + { "log_opened", "Log file {hilight $0} opened", 1, { 0 } }, + { "log_closed", "Log file {hilight $0} closed", 1, { 0 } }, + { "log_create_failed", "Couldn't create log file {hilight $0}: $1", 2, { 0, 0 } }, + { "log_locked", "Log file {hilight $0} is locked, probably by another running Irssi-SILC", 1, { 0 } }, + { "log_not_open", "Log file {hilight $0} not open", 1, { 0 } }, + { "log_started", "Started logging to file {hilight $0}", 1, { 0 } }, + { "log_stopped", "Stopped logging to file {hilight $0}", 1, { 0 } }, + { "log_list_header", "Logs:", 0 }, + { "log_list", "$0 $1: $2 $3$4", 5, { 1, 0, 0, 0, 0, 0 } }, + { "log_list_footer", "", 0 }, + { "windowlog_file", "Window LOGFILE set to $0", 1, { 0 } }, + { "windowlog_file_logging", "Can't change window's logfile while log is on", 0 }, + { "no_away_msgs", "No new messages in awaylog", 1, { 0 } }, + { "away_msgs", "{hilight $1} new messages in awaylog:", 2, { 0, 1 } }, + + /* ---- */ + { NULL, "Modules", 0 }, + + { "module_header", "Loaded modules:", 0, }, + { "module_line", " $0", 1, { 0 } }, + { "module_footer", "", 0, }, + { "module_already_loaded", "Module {hilight $0} already loaded", 1, { 0 } }, + { "module_load_error", "Error loading module {hilight $0}: $1", 2, { 0, 0 } }, + { "module_invalid", "{hilight $0} isn't Irssi-SILC module", 1, { 0 } }, + { "module_loaded", "Loaded module {hilight $0}", 1, { 0 } }, + { "module_unloaded", "Unloaded module {hilight $0}", 1, { 0 } }, + + /* ---- */ + { NULL, "Commands", 0 }, + + { "command_unknown", "Unknown command: $0", 1, { 0 } }, + { "command_ambiguous", "Ambiguous command: $0", 1, { 0 } }, + { "option_unknown", "Unknown option: $0", 1, { 0 } }, + { "option_ambiguous", "Ambiguous option: $0", 1, { 0 } }, + { "option_missing_arg", "Missing required argument for: $0", 1, { 0 } }, + { "not_enough_params", "Not enough parameters given", 0 }, + { "not_connected", "Not connected to server", 0 }, + { "not_joined", "Not joined to any channel", 0 }, + { "chan_not_found", "Not joined to such channel", 0 }, + { "chan_not_synced", "Channel not fully synchronized yet, try again after a while", 0 }, + { "not_good_idea", "Doing this is not a good idea. Add -YES if you really mean it", 0 }, + + /* ---- */ + { NULL, "Themes", 0 }, + + { "theme_saved", "Theme saved to $0", 1, { 0 } }, + { "theme_save_failed", "Error saving theme to $0: $1", 2, { 0, 0 } }, + { "theme_not_found", "Theme {hilight $0} not found", 1, { 0 } }, + { "theme_changed", "Using now theme {hilight $0} ($1)", 2, { 0, 0 } }, + { "window_theme_changed", "Using theme {hilight $0} ($1) in this window", 2, { 0, 0 } }, + { "format_title", "%:[{hilight $0}] - [{hilight $1}]%:", 2, { 0, 0 } }, + { "format_subtitle", "[{hilight $0}]", 1, { 0 } }, + { "format_item", "$0 = $1", 2, { 0, 0 } }, + + /* ---- */ + { NULL, "Ignores", 0 }, + + { "ignored", "Ignoring {hilight $1} from {nick $0}", 2, { 0, 0 } }, + { "unignored", "Unignored {nick $0}", 1, { 0 } }, + { "ignore_not_found", "{nick $0} is not being ignored", 1, { 0 } }, + { "ignore_no_ignores", "There are no ignores", 0 }, + { "ignore_header", "Ignorance List:", 0 }, + { "ignore_line", "$[-4]0 $1: $2 $3 $4", 4, { 1, 0, 0, 0 } }, + { "ignore_footer", "", 0 }, + + /* ---- */ + { NULL, "Misc", 0 }, + + { "unknown_chat_protocol", "Unknown chat protocol: $0", 1, { 0 } }, + { "unknown_chatnet", "Unknown chat network: $0", 1, { 0 } }, + { "not_toggle", "Value must be either ON, OFF or TOGGLE", 0 }, + { "perl_error", "Perl error: $0", 1, { 0 } }, + { "bind_key", "$[10]0 $1 $2", 3, { 0, 0, 0 } }, + { "bind_unknown_id", "Unknown bind action: $0", 1, { 0 } }, + { "config_saved", "Saved configuration to file $0", 1, { 0 } }, + { "config_reloaded", "Reloaded configuration", 1, { 0 } }, + { "config_modified", "Configuration file was modified since Irssi-SILC was last started - do you want to overwrite the possible changes?", 1, { 0 } }, + { "glib_error", "{error GLib $0} $1", 2, { 0, 0 } }, + { "overwrite_config", "Overwrite config (y/N)?", 0 }, + + { NULL, NULL, 0 } +}; diff --git a/apps/irssi/src/fe-common/core/module-formats.h b/apps/irssi/src/fe-common/core/module-formats.h new file mode 100644 index 00000000..e0f31f9a --- /dev/null +++ b/apps/irssi/src/fe-common/core/module-formats.h @@ -0,0 +1,194 @@ +#include "formats.h" + +enum { + TXT_MODULE_NAME, + + TXT_FILL_1, + + TXT_LINE_START, + TXT_LINE_START_IRSSI, + TXT_TIMESTAMP, + TXT_SERVERTAG, + TXT_DAYCHANGE, + TXT_TALKING_WITH, + TXT_REFNUM_TOO_LOW, + TXT_ERROR_SERVER_STICKY, + TXT_SET_SERVER_STICKY, + TXT_UNSET_SERVER_STICKY, + TXT_WINDOW_LEVEL, + TXT_WINDOWLIST_HEADER, + TXT_WINDOWLIST_LINE, + TXT_WINDOWLIST_FOOTER, + TXT_WINDOWS_LAYOUT_SAVED, + TXT_WINDOWS_LAYOUT_RESET, + + TXT_FILL_2, + + TXT_LOOKING_UP, + TXT_CONNECTING, + TXT_CONNECTION_ESTABLISHED, + TXT_CANT_CONNECT, + TXT_CONNECTION_LOST, + TXT_LAG_DISCONNECTED, + TXT_DISCONNECTED, + TXT_SERVER_QUIT, + TXT_SERVER_CHANGED, + TXT_UNKNOWN_SERVER_TAG, + TXT_NO_CONNECTED_SERVERS, + TXT_SERVER_LIST, + TXT_SERVER_LOOKUP_LIST, + TXT_SERVER_RECONNECT_LIST, + TXT_RECONNECT_REMOVED, + TXT_RECONNECT_NOT_FOUND, + TXT_SETUPSERVER_ADDED, + TXT_SETUPSERVER_REMOVED, + TXT_SETUPSERVER_NOT_FOUND, + + TXT_FILL_3, + + TXT_JOIN, + TXT_PART, + TXT_KICK, + TXT_QUIT, + TXT_QUIT_ONCE, + TXT_INVITE, + TXT_NEW_TOPIC, + TXT_TOPIC_UNSET, + TXT_YOUR_NICK_CHANGED, + TXT_NICK_CHANGED, + TXT_TALKING_IN, + TXT_NOT_IN_CHANNELS, + TXT_CURRENT_CHANNEL, + TXT_NAMES, + TXT_NAMES_NICK, + TXT_ENDOFNAMES, + TXT_CHANLIST_HEADER, + TXT_CHANLIST_LINE, + TXT_CHANSETUP_NOT_FOUND, + TXT_CHANSETUP_ADDED, + TXT_CHANSETUP_REMOVED, + TXT_CHANSETUP_HEADER, + TXT_CHANSETUP_LINE, + TXT_CHANSETUP_FOOTER, + TXT_CHANNEL_MOVE_NOTIFY, + + TXT_FILL_4, + + TXT_OWN_MSG, + TXT_OWN_MSG_CHANNEL, + TXT_OWN_MSG_PRIVATE, + TXT_OWN_MSG_PRIVATE_QUERY, + TXT_PUBMSG_ME, + TXT_PUBMSG_ME_CHANNEL, + TXT_PUBMSG_HILIGHT, + TXT_PUBMSG_HILIGHT_CHANNEL, + TXT_PUBMSG, + TXT_PUBMSG_CHANNEL, + TXT_MSG_PRIVATE, + TXT_MSG_PRIVATE_QUERY, + TXT_NO_MSGS_GOT, + TXT_NO_MSGS_SENT, + + TXT_FILL_5, + + TXT_QUERY_STARTED, + TXT_NO_QUERY, + TXT_QUERY_SERVER_CHANGED, + TXT_QUERY_MOVE_NOTIFY, + + TXT_FILL_6, + + TXT_HILIGHT_HEADER, + TXT_HILIGHT_LINE, + TXT_HILIGHT_FOOTER, + TXT_HILIGHT_NOT_FOUND, + TXT_HILIGHT_REMOVED, + + TXT_FILL_7, + + TXT_ALIAS_ADDED, + TXT_ALIAS_REMOVED, + TXT_ALIAS_NOT_FOUND, + TXT_ALIASLIST_HEADER, + TXT_ALIASLIST_LINE, + TXT_ALIASLIST_FOOTER, + + TXT_FILL_8, + + TXT_LOG_OPENED, + TXT_LOG_CLOSED, + TXT_LOG_CREATE_FAILED, + TXT_LOG_LOCKED, + TXT_LOG_NOT_OPEN, + TXT_LOG_STARTED, + TXT_LOG_STOPPED, + TXT_LOG_LIST_HEADER, + TXT_LOG_LIST, + TXT_LOG_LIST_FOOTER, + TXT_WINDOWLOG_FILE, + TXT_WINDOWLOG_FILE_LOGGING, + TXT_LOG_NO_AWAY_MSGS, + TXT_LOG_AWAY_MSGS, + + TXT_FILL_9, + + TXT_MODULE_HEADER, + TXT_MODULE_LINE, + TXT_MODULE_FOOTER, + TXT_MODULE_ALREADY_LOADED, + TXT_MODULE_LOAD_ERROR, + TXT_MODULE_INVALID, + TXT_MODULE_LOADED, + TXT_MODULE_UNLOADED, + + TXT_FILL_10, + + TXT_COMMAND_UNKNOWN, + TXT_COMMAND_AMBIGUOUS, + TXT_OPTION_UNKNOWN, + TXT_OPTION_AMBIGUOUS, + TXT_OPTION_MISSING_ARG, + TXT_NOT_ENOUGH_PARAMS, + TXT_NOT_CONNECTED, + TXT_NOT_JOINED, + TXT_CHAN_NOT_FOUND, + TXT_CHAN_NOT_SYNCED, + TXT_NOT_GOOD_IDEA, + + TXT_FILL_11, + + TXT_THEME_SAVED, + TXT_THEME_SAVE_FAILED, + TXT_THEME_NOT_FOUND, + TXT_THEME_CHANGED, + TXT_WINDOW_THEME_CHANGED, + TXT_FORMAT_TITLE, + TXT_FORMAT_SUBTITLE, + TXT_FORMAT_ITEM, + + TXT_FILL_12, + + TXT_IGNORED, + TXT_UNIGNORED, + TXT_IGNORE_NOT_FOUND, + TXT_IGNORE_NO_IGNORES, + TXT_IGNORE_HEADER, + TXT_IGNORE_LINE, + TXT_IGNORE_FOOTER, + + TXT_FILL_13, + + TXT_UNKNOWN_CHAT_PROTOCOL, + TXT_UNKNOWN_CHATNET, + TXT_NOT_TOGGLE, + TXT_PERL_ERROR, + TXT_BIND_KEY, + TXT_BIND_UNKNOWN_ID, + TXT_CONFIG_SAVED, + TXT_CONFIG_RELOADED, + TXT_CONFIG_MODIFIED, + TXT_GLIB_ERROR, + TXT_OVERWRITE_CONFIG +}; + +extern FORMAT_REC fecommon_core_formats[]; diff --git a/apps/irssi/src/fe-common/core/module.h b/apps/irssi/src/fe-common/core/module.h new file mode 100644 index 00000000..203e3a30 --- /dev/null +++ b/apps/irssi/src/fe-common/core/module.h @@ -0,0 +1,34 @@ +#include "common.h" + +#define MODULE_NAME "fe-common/core" + +typedef struct { + time_t time; + char *nick; + + /* channel specific msg to/from me - this is actually a reference + count. it begins from `completion_keep_publics' and is decreased + every time some nick is added to lastmsgs list. + + this is because of how the nick completion works. the same nick + is never in the lastmsgs list twice, but every time it's used + it's just moved to the beginning of the list. if this would be + just a boolean value the own-status would never be removed + from the nick if it didn't keep quiet for long enough. + + so, the own-status is rememberd only for the last + `completion_keep_publics' lines */ + int own; +} LAST_MSG_REC; + +typedef struct { + /* /MSG completion: */ + GSList *lastmsgs; /* list of nicks who sent you msg or + to who you send msg */ +} MODULE_SERVER_REC; + +typedef struct { + /* nick completion: */ + GSList *lastmsgs; /* list of nicks who sent latest msgs and + list of nicks who you sent msgs to */ +} MODULE_CHANNEL_REC; diff --git a/apps/irssi/src/fe-common/core/printtext.c b/apps/irssi/src/fe-common/core/printtext.c new file mode 100644 index 00000000..38de158d --- /dev/null +++ b/apps/irssi/src/fe-common/core/printtext.c @@ -0,0 +1,456 @@ +/* + printtext.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "commands.h" +#include "settings.h" + +#include "levels.h" +#include "servers.h" + +#include "themes.h" +#include "fe-windows.h" +#include "printtext.h" + +static int beep_msg_level, beep_when_away, beep_when_window_active; + +static int signal_gui_print_text; +static int signal_print_text_stripped; +static int signal_print_text; +static int signal_print_text_finished; +static int signal_print_format; +static int signal_print_starting; + +static int sending_print_starting; + +static void print_line(TEXT_DEST_REC *dest, const char *text); + +static void printformat_module_dest(const char *module, TEXT_DEST_REC *dest, + int formatnum, va_list va) +{ + char *arglist[MAX_FORMAT_PARAMS]; + char buffer[DEFAULT_FORMAT_ARGLIST_SIZE]; + FORMAT_REC *formats; + THEME_REC *theme; + char *str; + + theme = dest->window->theme == NULL ? current_theme : + dest->window->theme; + + formats = g_hash_table_lookup(default_formats, module); + format_read_arglist(va, &formats[formatnum], + arglist, sizeof(arglist)/sizeof(char *), + buffer, sizeof(buffer)); + + if (!sending_print_starting) { + sending_print_starting = TRUE; + signal_emit_id(signal_print_starting, 1, dest); + sending_print_starting = FALSE; + } + + signal_emit_id(signal_print_format, 5, theme, module, + dest, GINT_TO_POINTER(formatnum), arglist); + + str = format_get_text_theme_charargs(theme, module, dest, + formatnum, arglist); + if (*str != '\0') print_line(dest, str); + g_free(str); +} + +void printformat_module_args(const char *module, void *server, + const char *target, int level, + int formatnum, va_list va) +{ + TEXT_DEST_REC dest; + + format_create_dest(&dest, server, target, level, NULL); + printformat_module_dest(module, &dest, formatnum, va); +} + +void printformat_module(const char *module, void *server, const char *target, + int level, int formatnum, ...) +{ + va_list va; + + va_start(va, formatnum); + printformat_module_args(module, server, target, level, formatnum, va); + va_end(va); +} + +void printformat_module_window_args(const char *module, WINDOW_REC *window, + int level, int formatnum, va_list va) +{ + TEXT_DEST_REC dest; + + format_create_dest(&dest, NULL, NULL, level, window); + printformat_module_dest(module, &dest, formatnum, va); +} + +void printformat_module_window(const char *module, WINDOW_REC *window, + int level, int formatnum, ...) +{ + va_list va; + + va_start(va, formatnum); + printformat_module_window_args(module, window, level, formatnum, va); + va_end(va); +} + +void printformat_module_gui_args(const char *module, int formatnum, va_list va) +{ + TEXT_DEST_REC dest; + char *arglist[MAX_FORMAT_PARAMS]; + char buffer[DEFAULT_FORMAT_ARGLIST_SIZE]; + FORMAT_REC *formats; + char *str; + + g_return_if_fail(module != NULL); + + memset(&dest, 0, sizeof(dest)); + + formats = g_hash_table_lookup(default_formats, module); + format_read_arglist(va, &formats[formatnum], + arglist, sizeof(arglist)/sizeof(char *), + buffer, sizeof(buffer)); + + str = format_get_text_theme_charargs(current_theme, module, &dest, + formatnum, arglist); + if (*str != '\0') format_send_to_gui(&dest, str); + g_free(str); +} + +void printformat_module_gui(const char *module, int formatnum, ...) +{ + va_list va; + + va_start(va, formatnum); + printformat_module_gui_args(module, formatnum, va); + va_end(va); +} + +static void print_line(TEXT_DEST_REC *dest, const char *text) +{ + char *str, *tmp; + + g_return_if_fail(dest != NULL); + g_return_if_fail(text != NULL); + + tmp = format_get_level_tag(current_theme, dest); + str = format_add_linestart(text, tmp); + g_free_not_null(tmp); + + /* send the plain text version for logging etc.. */ + tmp = strip_codes(str); + signal_emit_id(signal_print_text_stripped, 2, dest, tmp); + g_free(tmp); + + signal_emit_id(signal_print_text, 2, dest, str); + g_free(str); +} + +/* append string to `out', expand newlines. */ +static void printtext_append_str(TEXT_DEST_REC *dest, GString *out, + const char *str) +{ + while (*str != '\0') { + if (*str != '\n') + g_string_append_c(out, *str); + else { + print_line(dest, out->str); + g_string_truncate(out, 0); + } + str++; + } +} + +static char *printtext_get_args(TEXT_DEST_REC *dest, const char *str, + va_list va) +{ + GString *out; + char *ret; + + out = g_string_new(NULL); + for (; *str != '\0'; str++) { + if (*str != '%') { + g_string_append_c(out, *str); + continue; + } + + if (*++str == '\0') + break; + + /* standard parameters */ + switch (*str) { + case 's': { + char *s = (char *) va_arg(va, char *); + if (s && *s) printtext_append_str(dest, out, s); + break; + } + case 'd': { + int d = (int) va_arg(va, int); + g_string_sprintfa(out, "%d", d); + break; + } + case 'f': { + double f = (double) va_arg(va, double); + g_string_sprintfa(out, "%0.2f", f); + break; + } + case 'u': { + unsigned int d = + (unsigned int) va_arg(va, unsigned int); + g_string_sprintfa(out, "%u", d); + break; + } + case 'l': { + long d = (long) va_arg(va, long); + + if (*++str != 'd' && *str != 'u') { + g_string_sprintfa(out, "%ld", d); + str--; + } else { + if (*str == 'd') + g_string_sprintfa(out, "%ld", d); + else + g_string_sprintfa(out, "%lu", d); + } + break; + } + default: + if (!format_expand_styles(out, *str)) { + g_string_append_c(out, '%'); + g_string_append_c(out, *str); + } + break; + } + } + + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +static char *printtext_expand_formats(const char *str) +{ + GString *out; + char *ret; + + out = g_string_new(NULL); + for (; *str != '\0'; str++) { + if (*str != '%') { + g_string_append_c(out, *str); + continue; + } + + if (*++str == '\0') + break; + + if (!format_expand_styles(out, *str)) { + g_string_append_c(out, '%'); + g_string_append_c(out, *str); + } + } + + ret = out->str; + g_string_free(out, FALSE); + return ret; +} + +void printtext_dest(TEXT_DEST_REC *dest, const char *text, va_list va) +{ + char *str; + + if (!sending_print_starting) { + sending_print_starting = TRUE; + signal_emit_id(signal_print_starting, 1, dest); + sending_print_starting = FALSE; + } + + str = printtext_get_args(dest, text, va); + print_line(dest, str); + g_free(str); +} + +/* Write text to target - convert color codes */ +void printtext(void *server, const char *target, int level, const char *text, ...) +{ + TEXT_DEST_REC dest; + va_list va; + + g_return_if_fail(text != NULL); + + format_create_dest(&dest, server, target, level, NULL); + + va_start(va, text); + printtext_dest(&dest, text, va); + va_end(va); +} + +/* Like printtext(), but don't handle %s etc. */ +void printtext_string(void *server, const char *target, int level, const char *text) +{ + TEXT_DEST_REC dest; + char *str; + + g_return_if_fail(text != NULL); + + format_create_dest(&dest, server, target, level, NULL); + + if (!sending_print_starting) { + sending_print_starting = TRUE; + signal_emit_id(signal_print_starting, 1, dest); + sending_print_starting = FALSE; + } + + str = printtext_expand_formats(text); + print_line(&dest, str); + g_free(str); +} + +void printtext_window(WINDOW_REC *window, int level, const char *text, ...) +{ + TEXT_DEST_REC dest; + va_list va; + + g_return_if_fail(text != NULL); + + format_create_dest(&dest, NULL, NULL, level, + window != NULL ? window : active_win); + + va_start(va, text); + printtext_dest(&dest, text, va); + va_end(va); +} + +void printtext_gui(const char *text) +{ + TEXT_DEST_REC dest; + char *str; + + g_return_if_fail(text != NULL); + + memset(&dest, 0, sizeof(dest)); + + str = printtext_expand_formats(text); + format_send_to_gui(&dest, str); + g_free(str); +} + +static void msg_beep_check(TEXT_DEST_REC *dest) +{ + if (dest->level != 0 && (dest->level & MSGLEVEL_NOHILIGHT) == 0 && + (beep_msg_level & dest->level) && + (beep_when_away || (dest->server != NULL && + !dest->server->usermode_away)) && + (beep_when_window_active || dest->window != active_win)) { + signal_emit("beep", 0); + } +} + +static void sig_print_text(TEXT_DEST_REC *dest, const char *text) +{ + char *str, *tmp; + + g_return_if_fail(dest != NULL); + g_return_if_fail(text != NULL); + + msg_beep_check(dest); + + dest->window->last_line = time(NULL); + + /* add timestamp/server tag here - if it's done in print_line() + it would be written to log files too */ + tmp = format_get_line_start(current_theme, dest, time(NULL)); + str = format_add_linestart(text, tmp); + g_free_not_null(tmp); + + format_send_to_gui(dest, str); + g_free(str); + + signal_emit_id(signal_print_text_finished, 1, dest->window); +} + +static void sig_print_text_free(TEXT_DEST_REC *dest, const char *text) +{ + g_free_and_null(dest->hilight_color); +} + +void printtext_multiline(void *server, const char *target, int level, + const char *format, const char *text) +{ + char **lines, **tmp; + + g_return_if_fail(format != NULL); + g_return_if_fail(text != NULL); + + lines = g_strsplit(text, "\n", -1); + for (tmp = lines; *tmp != NULL; tmp++) + printtext(NULL, NULL, level, format, *tmp); + g_strfreev(lines); +} + +static void sig_gui_dialog(const char *type, const char *text) +{ + char *format; + + if (g_strcasecmp(type, "warning") == 0) + format = "%_Warning:%_ %s"; + else if (g_strcasecmp(type, "error") == 0) + format = "%_Error:%_ %s"; + else + format = "%s"; + + printtext_multiline(NULL, NULL, MSGLEVEL_NEVER, format, text); +} + +static void read_settings(void) +{ + beep_msg_level = level2bits(settings_get_str("beep_msg_level")); + beep_when_away = settings_get_bool("beep_when_away"); + beep_when_window_active = settings_get_bool("beep_when_window_active"); +} + +void printtext_init(void) +{ + sending_print_starting = FALSE; + signal_gui_print_text = signal_get_uniq_id("gui print text"); + signal_print_text_stripped = signal_get_uniq_id("print text stripped"); + signal_print_text = signal_get_uniq_id("print text"); + signal_print_text_finished = signal_get_uniq_id("print text finished"); + signal_print_format = signal_get_uniq_id("print format"); + signal_print_starting = signal_get_uniq_id("print starting"); + + read_settings(); + signal_add("print text", (SIGNAL_FUNC) sig_print_text); + signal_add_last("print text", (SIGNAL_FUNC) sig_print_text_free); + signal_add("gui dialog", (SIGNAL_FUNC) sig_gui_dialog); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void printtext_deinit(void) +{ + signal_remove("print text", (SIGNAL_FUNC) sig_print_text); + signal_remove("print text", (SIGNAL_FUNC) sig_print_text_free); + signal_remove("gui dialog", (SIGNAL_FUNC) sig_gui_dialog); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/fe-common/core/printtext.h b/apps/irssi/src/fe-common/core/printtext.h new file mode 100644 index 00000000..14f46564 --- /dev/null +++ b/apps/irssi/src/fe-common/core/printtext.h @@ -0,0 +1,93 @@ +#ifndef __PRINTTEXT_H +#define __PRINTTEXT_H + +#include "fe-windows.h" + +void printformat_module(const char *module, void *server, const char *target, int level, int formatnum, ...); +void printformat_module_window(const char *module, WINDOW_REC *window, int level, int formatnum, ...); + +void printformat_module_args(const char *module, void *server, const char *target, int level, int formatnum, va_list va); +void printformat_module_window_args(const char *module, WINDOW_REC *window, int level, int formatnum, va_list va); + +void printtext(void *server, const char *target, int level, const char *text, ...); +void printtext_string(void *server, const char *target, int level, const char *text); +void printtext_window(WINDOW_REC *window, int level, const char *text, ...); +void printtext_multiline(void *server, const char *target, int level, const char *format, const char *text); + +/* only GUI should call these - used for printing text to somewhere else + than windows */ +void printtext_gui(const char *text); +void printformat_module_gui(const char *module, int formatnum, ...); +void printformat_module_gui_args(const char *module, int formatnum, va_list va); + +void printtext_init(void); +void printtext_deinit(void); + +/* printformat(...) = printformat_format(MODULE_NAME, ...) + + Could this be any harder? :) With GNU C compiler and C99 compilers, + use #define. With others use either inline functions if they are + supported or static functions if they are not.. + */ +#if defined (__GNUC__) && !defined (__STRICT_ANSI__) +/* GCC */ +# define printformat(server, target, level, formatnum...) \ + printformat_module(MODULE_NAME, server, target, level, ##formatnum) +# define printformat_window(window, level, formatnum...) \ + printformat_module_window(MODULE_NAME, window, level, ##formatnum) +# define printformat_gui(formatnum...) \ + printformat_module_gui(MODULE_NAME, ##formatnum) +#elif defined (_ISOC99_SOURCE) +/* C99 */ +# define printformat(server, target, level, formatnum, ...) \ + printformat_module(MODULE_NAME, server, target, level, formatnum, __VA_ARGS__) +# define printformat_window(window, level, formatnum, ...) \ + printformat_module_window(MODULE_NAME, window, level, formatnum, __VA_ARGS__) +# define printformat_gui(formatnum, ...) \ + printformat_module_gui(MODULE_NAME, formatnum, __VA_ARGS__) +#else +/* inline/static */ +#ifdef G_CAN_INLINE +G_INLINE_FUNC +#else +static +#endif +void printformat(void *server, const char *target, int level, int formatnum, ...) +{ + va_list va; + + va_start(va, formatnum); + printformat_module_args(MODULE_NAME, server, target, level, formatnum, va); + va_end(va); +} + +#ifdef G_CAN_INLINE +G_INLINE_FUNC +#else +static +#endif +void printformat_window(WINDOW_REC *window, int level, int formatnum, ...) +{ + va_list va; + + va_start(va, formatnum); + printformat_module_window_args(MODULE_NAME, window, level, formatnum, va); + va_end(va); +} + +#ifdef G_CAN_INLINE +G_INLINE_FUNC +#else +static +#endif +void printformat_gui(int formatnum, ...) +{ + va_list va; + + va_start(va, formatnum); + printformat_module_gui_args(MODULE_NAME, formatnum, va); + va_end(va); +} +#endif + +#endif diff --git a/apps/irssi/src/fe-common/core/themes.c b/apps/irssi/src/fe-common/core/themes.c new file mode 100644 index 00000000..ab768ccb --- /dev/null +++ b/apps/irssi/src/fe-common/core/themes.c @@ -0,0 +1,1184 @@ +/* + themes.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "levels.h" +#include "misc.h" +#include "special-vars.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "themes.h" +#include "printtext.h" + +#include "default-theme.h" + +GSList *themes; +THEME_REC *current_theme; +GHashTable *default_formats; + +static int init_finished; +static char *init_errors; + +static int theme_read(THEME_REC *theme, const char *path, const char *data); + +THEME_REC *theme_create(const char *path, const char *name) +{ + THEME_REC *rec; + + g_return_val_if_fail(path != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + rec = g_new0(THEME_REC, 1); + rec->path = g_strdup(path); + rec->name = g_strdup(name); + rec->abstracts = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + rec->modules = g_hash_table_new((GHashFunc) g_istr_hash, + (GCompareFunc) g_istr_equal); + themes = g_slist_append(themes, rec); + signal_emit("theme created", 1, rec); + + return rec; +} + +static void theme_abstract_destroy(char *key, char *value) +{ + g_free(key); + g_free(value); +} + +static void theme_module_destroy(const char *key, MODULE_THEME_REC *rec) +{ + int n; + + for (n = 0; n < rec->count; n++) { + g_free_not_null(rec->formats[n]); + g_free_not_null(rec->expanded_formats[n]); + } + g_free(rec->formats); + g_free(rec->expanded_formats); + + g_free(rec->name); + g_free(rec); +} + +void theme_destroy(THEME_REC *rec) +{ + themes = g_slist_remove(themes, rec); + + signal_emit("theme destroyed", 1, rec); + + g_hash_table_foreach(rec->abstracts, (GHFunc) theme_abstract_destroy, NULL); + g_hash_table_destroy(rec->abstracts); + g_hash_table_foreach(rec->modules, (GHFunc) theme_module_destroy, NULL); + g_hash_table_destroy(rec->modules); + + g_slist_foreach(rec->replace_values, (GFunc) g_free, NULL); + g_slist_free(rec->replace_values); + + g_free(rec->path); + g_free(rec->name); + g_free(rec); +} + +static char *theme_replace_expand(THEME_REC *theme, int index, + char default_fg, char default_bg, + char *last_fg, char *last_bg, + char chr, int flags) +{ + GSList *rec; + char *ret, *abstract, data[2]; + + rec = g_slist_nth(theme->replace_values, index); + g_return_val_if_fail(rec != NULL, NULL); + + data[0] = chr; data[1] = '\0'; + + abstract = rec->data; + abstract = theme_format_expand_data(theme, (const char **) &abstract, + default_fg, default_bg, + last_fg, last_bg, flags); + ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0); + g_free(abstract); + return ret; +} + +static const char *fgcolorformats = "nkrgybmpcwKRGYBMPCW"; +static const char *bgcolorformats = "n01234567"; + +#define IS_FGCOLOR_FORMAT(c) \ + ((c) != '\0' && strchr(fgcolorformats, (c)) != NULL) +#define IS_BGCOLOR_FORMAT(c) \ + ((c) != '\0' && strchr(bgcolorformats, (c)) != NULL) + +/* append "variable" part in $variable, ie. not the contents of the variable */ +static void theme_format_append_variable(GString *str, const char **format) +{ + const char *orig; + char *value, *args[1] = { NULL }; + int free_ret; + + orig = *format; + (*format)++; + + value = parse_special((char **) format, NULL, NULL, + args, &free_ret, NULL, 0); + if (free_ret) g_free(value); + (*format)++; + + /* append the variable name */ + value = g_strndup(orig, (int) (*format-orig)); + g_string_append(str, value); + g_free(value); +} + +/* append next "item", either a character, $variable or %format */ +static void theme_format_append_next(THEME_REC *theme, GString *str, + const char **format, + char default_fg, char default_bg, + char *last_fg, char *last_bg, + int flags) +{ + int index; + unsigned char chr; + + chr = **format; + if ((chr == '$' || chr == '%') && + (*format)[1] == '\0') { + /* last char, always append */ + g_string_append_c(str, chr); + (*format)++; + return; + } + + if (chr == '$') { + /* $variable .. we'll always need to skip this, since it + may contain characters that are in replace chars. */ + theme_format_append_variable(str, format); + return; + } + + if (**format == '%') { + /* format */ + (*format)++; + if (**format != '{' && **format != '}') { + chr = **format; + if (**format == 'n') { + /* %n = change to default color */ + g_string_append(str, "%n"); + + if (default_bg != 'n') { + g_string_append_c(str, '%'); + g_string_append_c(str, default_bg); + } + if (default_fg != 'n') { + g_string_append_c(str, '%'); + g_string_append_c(str, default_fg); + } + + *last_fg = default_fg; + *last_bg = default_bg; + } else { + if (IS_FGCOLOR_FORMAT(chr)) + *last_fg = chr; + if (IS_BGCOLOR_FORMAT(chr)) + *last_bg = chr; + g_string_append_c(str, '%'); + g_string_append_c(str, chr); + } + (*format)++; + return; + } + + /* %{ or %} gives us { or } char */ + chr = **format; + } + + index = (flags & EXPAND_FLAG_IGNORE_REPLACES) ? -1 : + theme->replace_keys[(int) chr]; + if (index == -1) + g_string_append_c(str, chr); + else { + char *value; + + value = theme_replace_expand(theme, index, + default_fg, default_bg, + last_fg, last_bg, chr, flags); + g_string_append(str, value); + g_free(value); + } + + (*format)++; +} + +/* expand a single {abstract ...data... } */ +static char *theme_format_expand_abstract(THEME_REC *theme, + const char **formatp, + char default_fg, char default_bg, + int flags) +{ + const char *p, *format; + char *abstract, *data, *ret; + int len; + + format = *formatp; + + /* get abstract name first */ + p = format; + while (*p != '\0' && *p != ' ' && + *p != '{' && *p != '}') p++; + if (*p == '\0' || p == format) + return NULL; /* error */ + + len = (int) (p-format); + abstract = g_strndup(format, len); + + /* skip the following space, if there's any more spaces they're + treated as arguments */ + if (*p == ' ') { + len++; + if ((flags & EXPAND_FLAG_IGNORE_EMPTY)) { + /* if the data is empty, ignore the abstract */ + p = format+len; + while (*p == ' ') p++; + if (*p == '}') { + *formatp = p+1; + g_free(abstract); + return NULL; + } + } + + } + *formatp = format+len; + + /* get the abstract data */ + data = g_hash_table_lookup(theme->abstracts, abstract); + g_free(abstract); + if (data == NULL) { + /* unknown abstract, just display the data */ + data = "$0-"; + } + abstract = g_strdup(data); + + /* we'll need to get the data part. it may contain + more abstracts, they are automatically expanded. */ + data = theme_format_expand_data(theme, formatp, default_fg, default_bg, + NULL, NULL, flags); + len = strlen(data); + + if (len > 1 && isdigit(data[len-1]) && data[len-2] == '$') { + /* ends with $ .. this breaks things if next + character is digit or '-' */ + char digit, *tmp; + + tmp = data; + digit = tmp[len-1]; + tmp[len-1] = '\0'; + + data = g_strdup_printf("%s{%c}", tmp, digit); + g_free(tmp); + } + + ret = parse_special_string(abstract, NULL, NULL, data, NULL, 0); + g_free(abstract); + g_free(data); + abstract = ret; + + /* abstract may itself contain abstracts or replaces */ + p = abstract; + ret = theme_format_expand_data(theme, &p, default_fg, default_bg, + &default_fg, &default_bg, + flags | EXPAND_FLAG_LASTCOLOR_ARG); + g_free(abstract); + return ret; +} + +/* expand the data part in {abstract data} */ +char *theme_format_expand_data(THEME_REC *theme, const char **format, + char default_fg, char default_bg, + char *save_last_fg, char *save_last_bg, + int flags) +{ + GString *str; + char *ret, *abstract; + char last_fg, last_bg; + int recurse_flags; + + last_fg = default_fg; + last_bg = default_bg; + recurse_flags = flags & EXPAND_FLAG_RECURSIVE_MASK; + + str = g_string_new(NULL); + while (**format != '\0') { + if ((flags & EXPAND_FLAG_ROOT) == 0 && **format == '}') { + /* ignore } if we're expanding original string */ + (*format)++; + break; + } + + if (**format != '{') { + if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) && + **format == '$' && (*format)[1] == '0') { + /* save the color before $0 .. + this is for the %n replacing */ + if (save_last_fg != NULL) { + *save_last_fg = last_fg; + save_last_fg = NULL; + } + if (save_last_bg != NULL) { + *save_last_bg = last_bg; + save_last_bg = NULL; + } + } + + theme_format_append_next(theme, str, format, + default_fg, default_bg, + &last_fg, &last_bg, + recurse_flags); + continue; + } + + (*format)++; + if (**format == '\0' || **format == '}') + break; /* error */ + + /* get a single {...} */ + abstract = theme_format_expand_abstract(theme, format, + last_fg, last_bg, + recurse_flags); + if (abstract != NULL) { + g_string_append(str, abstract); + g_free(abstract); + } + } + + if ((flags & EXPAND_FLAG_LASTCOLOR_ARG) == 0) { + /* save the last color */ + if (save_last_fg != NULL) + *save_last_fg = last_fg; + if (save_last_bg != NULL) + *save_last_bg = last_bg; + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +#define IS_OLD_FORMAT(code, last_fg, last_bg) \ + (((code) == 'n' && (last_fg) == 'n' && (last_bg) == 'n') || \ + ((code) != 'n' && ((code) == (last_fg) || (code) == (last_bg)))) + +static char *theme_format_compress_colors(THEME_REC *theme, const char *format) +{ + GString *str; + char *ret, last_fg, last_bg; + + str = g_string_new(NULL); + + last_fg = last_bg = 'n'; + while (*format != '\0') { + if (*format == '$') { + /* $variable, skrip it entirely */ + theme_format_append_variable(str, &format); + last_fg = last_bg = '\0'; + } else if (*format != '%') { + /* a normal character */ + g_string_append_c(str, *format); + format++; + } else { + /* %format */ + format++; + if (IS_OLD_FORMAT(*format, last_fg, last_bg)) { + /* active color set again */ + } else if (IS_FGCOLOR_FORMAT(*format) && + format[1] == '%' && + IS_FGCOLOR_FORMAT(format[2]) && + (*format != 'n' || format[2] == 'n')) { + /* two fg colors in a row. bg colors are + so rare that we don't bother checking + them */ + } else { + /* some format, add it */ + g_string_append_c(str, '%'); + g_string_append_c(str, *format); + + if (IS_FGCOLOR_FORMAT(*format)) + last_fg = *format; + if (IS_BGCOLOR_FORMAT(*format)) + last_bg = *format; + } + format++; + } + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +char *theme_format_expand(THEME_REC *theme, const char *format) +{ + char *data, *ret; + + g_return_val_if_fail(theme != NULL, NULL); + g_return_val_if_fail(format != NULL, NULL); + + data = theme_format_expand_data(theme, &format, 'n', 'n', NULL, NULL, + EXPAND_FLAG_ROOT); + ret = theme_format_compress_colors(theme, data); + g_free(data); + return ret; +} + +static MODULE_THEME_REC *theme_module_create(THEME_REC *theme, const char *module) +{ + MODULE_THEME_REC *rec; + FORMAT_REC *formats; + + rec = g_hash_table_lookup(theme->modules, module); + if (rec != NULL) return rec; + + formats = g_hash_table_lookup(default_formats, module); + g_return_val_if_fail(formats != NULL, NULL); + + rec = g_new0(MODULE_THEME_REC, 1); + rec->name = g_strdup(module); + + for (rec->count = 0; formats[rec->count].def != NULL; rec->count++) ; + rec->formats = g_new0(char *, rec->count); + rec->expanded_formats = g_new0(char *, rec->count); + + g_hash_table_insert(theme->modules, rec->name, rec); + return rec; +} + +static void theme_read_replaces(CONFIG_REC *config, THEME_REC *theme) +{ + GSList *tmp; + CONFIG_NODE *node; + const char *p; + int index; + + /* reset replace keys */ + for (index = 0; index < 256; index++) + theme->replace_keys[index] = -1; + index = 0; + + node = config_node_traverse(config, "replaces", FALSE); + if (node == NULL || node->type != NODE_TYPE_BLOCK) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->key != NULL && node->value != NULL) { + for (p = node->key; *p != '\0'; p++) + theme->replace_keys[(int) *p] = index; + + theme->replace_values = + g_slist_append(theme->replace_values, + g_strdup(node->value)); + index++; + } + } +} + +static void theme_read_abstracts(CONFIG_REC *config, THEME_REC *theme) +{ + GSList *tmp; + CONFIG_NODE *node; + gpointer oldkey, oldvalue; + + node = config_node_traverse(config, "abstracts", FALSE); + if (node == NULL || node->type != NODE_TYPE_BLOCK) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->key == NULL || node->value == NULL) + continue; + + if (g_hash_table_lookup_extended(theme->abstracts, node->key, + &oldkey, &oldvalue)) { + /* new values override old ones */ + g_hash_table_remove(theme->abstracts, oldkey); + g_free(oldkey); + g_free(oldvalue); + } + + g_hash_table_insert(theme->abstracts, g_strdup(node->key), + g_strdup(node->value)); + } +} + +static void theme_set_format(THEME_REC *theme, MODULE_THEME_REC *rec, + const char *module, + const char *key, const char *value) +{ + int num; + + num = format_find_tag(module, key); + if (num != -1) { + rec->formats[num] = g_strdup(value); + rec->expanded_formats[num] = theme_format_expand(theme, value); + } +} + +static void theme_read_formats(THEME_REC *theme, const char *module, + CONFIG_REC *config, MODULE_THEME_REC *rec) +{ + CONFIG_NODE *node; + GSList *tmp; + + node = config_node_traverse(config, "formats", FALSE); + if (node == NULL) return; + node = config_node_section(node, module, -1); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + node = tmp->data; + + if (node->key != NULL && node->value != NULL) { + theme_set_format(theme, rec, module, + node->key, node->value); + } + } +} + +static void theme_init_module(THEME_REC *theme, const char *module, + CONFIG_REC *config) +{ + MODULE_THEME_REC *rec; + FORMAT_REC *formats; + int n; + + formats = g_hash_table_lookup(default_formats, module); + g_return_if_fail(formats != NULL); + + rec = theme_module_create(theme, module); + + if (config != NULL) + theme_read_formats(theme, module, config, rec); + + /* expand the remaining formats */ + for (n = 0; n < rec->count; n++) { + if (rec->expanded_formats[n] == NULL) { + rec->expanded_formats[n] = + theme_format_expand(theme, formats[n].def); + } + } +} + +static void sig_print_errors(void) +{ + init_finished = TRUE; + + if (init_errors != NULL) { + signal_emit("gui dialog", 2, "error", init_errors); + g_free(init_errors); + } +} + +static void theme_read_module(THEME_REC *theme, const char *module) +{ + CONFIG_REC *config; + + config = config_open(theme->path, -1); + if (config != NULL) + config_parse(config); + + theme_init_module(theme, module, config); + + if (config != NULL) config_close(config); +} + +static void themes_read_module(const char *module) +{ + g_slist_foreach(themes, (GFunc) theme_read_module, (void *) module); +} + +static void theme_remove_module(THEME_REC *theme, const char *module) +{ + MODULE_THEME_REC *rec; + + rec = g_hash_table_lookup(theme->modules, module); + if (rec == NULL) return; + + g_hash_table_remove(theme->modules, module); + theme_module_destroy(module, rec); +} + +static void themes_remove_module(const char *module) +{ + g_slist_foreach(themes, (GFunc) theme_remove_module, (void *) module); +} + +void theme_register_module(const char *module, FORMAT_REC *formats) +{ + if (g_hash_table_lookup(default_formats, module) != NULL) + return; + + g_hash_table_insert(default_formats, g_strdup(module), formats); + themes_read_module(module); +} + +void theme_unregister_module(const char *module) +{ + gpointer key, value; + + if (default_formats == NULL) + return; /* already uninitialized */ + + if (!g_hash_table_lookup_extended(default_formats, module, &key, &value)) + return; + + g_hash_table_remove(default_formats, key); + g_free(key); + + themes_remove_module(module); +} + +static THEME_REC *theme_find(const char *name) +{ + GSList *tmp; + + for (tmp = themes; tmp != NULL; tmp = tmp->next) { + THEME_REC *rec = tmp->data; + + if (g_strcasecmp(rec->name, name) == 0) + return rec; + } + + return NULL; +} + +static void window_themes_update(void) +{ + GSList *tmp; + + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->theme_name != NULL) + rec->theme = theme_load(rec->theme_name); + } +} + +THEME_REC *theme_load(const char *setname) +{ + THEME_REC *theme, *oldtheme; + struct stat statbuf; + char *fname, *name, *p; + + name = g_strdup(setname); + p = strrchr(name, '.'); + if (p != NULL && strcmp(p, ".theme") == 0) { + /* remove the trailing .theme */ + *p = '\0'; + } + + theme = theme_find(name); + + /* check home dir */ + fname = g_strdup_printf("%s/.silc/%s.theme", g_get_home_dir(), name); + if (stat(fname, &statbuf) != 0) { + /* check global config dir */ + g_free(fname); + fname = g_strdup_printf(SYSCONFDIR"/irssi/%s.theme", name); + if (stat(fname, &statbuf) != 0) { + /* theme not found */ + g_free(fname); + g_free(name); + return theme; /* use the one in memory if possible */ + } + } + + if (theme != NULL && theme->last_modify == statbuf.st_mtime) { + /* theme not modified, use the one already in memory */ + g_free(fname); + g_free(name); + return theme; + } + + oldtheme = theme; + theme = theme_create(fname, name); + theme->last_modify = statbuf.st_mtime; + if (!theme_read(theme, theme->path, NULL)) { + /* error reading .theme file */ + theme_destroy(theme); + theme = NULL; + } + + if (oldtheme != NULL && theme != NULL) { + theme_destroy(oldtheme); + window_themes_update(); + } + + g_free(fname); + g_free(name); + return theme; +} + +typedef struct { + THEME_REC *theme; + CONFIG_REC *config; +} THEME_READ_REC; + +static void theme_read_modules(const char *module, void *value, + THEME_READ_REC *rec) +{ + theme_init_module(rec->theme, module, rec->config); +} + +static void read_error(const char *str) +{ + char *old; + + if (init_finished) + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str); + else if (init_errors == NULL) + init_errors = g_strdup(str); + else { + old = init_errors; + init_errors = g_strconcat(init_errors, "\n", str, NULL); + g_free(old); + } +} + +static int theme_read(THEME_REC *theme, const char *path, const char *data) +{ + CONFIG_REC *config; + THEME_READ_REC rec; + char *str; + + config = config_open(data == NULL ? path : NULL, -1) ; + if (config == NULL) { + /* didn't exist or no access? */ + str = g_strdup_printf("Error reading theme file %s: %s", + path, g_strerror(errno)); + read_error(str); + g_free(str); + return FALSE; + } + + if (data != NULL) + config_parse_data(config, data, "internal"); + else + config_parse(config); + + if (config_last_error(config) != NULL) { + str = g_strdup_printf("Ignored errors in theme %s:\n%s", + theme->name, config_last_error(config)); + read_error(str); + g_free(str); + } + + theme->default_color = + config_get_int(config, NULL, "default_color", 0); + theme->default_real_color = + config_get_int(config, NULL, "default_real_color", 7); + theme_read_replaces(config, theme); + + if (data == NULL) { + /* get the default abstracts from default theme. */ + CONFIG_REC *default_config; + + default_config = config_open(NULL, -1); + config_parse_data(default_config, default_theme, "internal"); + theme_read_abstracts(default_config, theme); + config_close(default_config); + } + theme_read_abstracts(config, theme); + + rec.theme = theme; + rec.config = config; + g_hash_table_foreach(default_formats, + (GHFunc) theme_read_modules, &rec); + config_close(config); + + return TRUE; +} + +typedef struct { + char *name; + char *short_name; +} THEME_SEARCH_REC; + +static int theme_search_equal(THEME_SEARCH_REC *r1, THEME_SEARCH_REC *r2) +{ + return g_strcasecmp(r1->short_name, r2->short_name); +} + +static void theme_get_modules(char *module, FORMAT_REC *formats, GSList **list) +{ + THEME_SEARCH_REC *rec; + + rec = g_new(THEME_SEARCH_REC, 1); + rec->name = module; + rec->short_name = strrchr(module, '/'); + if (rec->short_name != NULL) + rec->short_name++; else rec->short_name = module; + *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) theme_search_equal); +} + +static GSList *get_sorted_modules(void) +{ + GSList *list; + + list = NULL; + g_hash_table_foreach(default_formats, (GHFunc) theme_get_modules, &list); + return list; +} + +static THEME_SEARCH_REC *theme_search(GSList *list, const char *module) +{ + THEME_SEARCH_REC *rec; + + while (list != NULL) { + rec = list->data; + + if (g_strcasecmp(rec->short_name, module) == 0) + return rec; + list = list->next; + } + + return NULL; +} + +static void theme_show(THEME_SEARCH_REC *rec, const char *key, const char *value, int reset) +{ + MODULE_THEME_REC *theme; + FORMAT_REC *formats; + const char *text, *last_title; + int n, first; + + formats = g_hash_table_lookup(default_formats, rec->name); + theme = g_hash_table_lookup(current_theme->modules, rec->name); + + last_title = NULL; first = TRUE; + for (n = 1; formats[n].def != NULL; n++) { + text = theme != NULL && theme->formats[n] != NULL ? + theme->formats[n] : formats[n].def; + + if (formats[n].tag == NULL) + last_title = text; + else if ((value != NULL && key != NULL && g_strcasecmp(formats[n].tag, key) == 0) || + (value == NULL && (key == NULL || stristr(formats[n].tag, key) != NULL))) { + if (first) { + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_TITLE, rec->short_name, formats[0].def); + first = FALSE; + } + if (last_title != NULL) + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_SUBTITLE, last_title); + if (reset || value != NULL) { + theme = theme_module_create(current_theme, rec->name); + g_free_not_null(theme->formats[n]); + g_free_not_null(theme->expanded_formats[n]); + + text = reset ? formats[n].def : value; + theme->formats[n] = reset ? NULL : g_strdup(value); + theme->expanded_formats[n] = theme_format_expand(current_theme, text); + } + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_FORMAT_ITEM, formats[n].tag, text); + last_title = NULL; + } + } +} + +/* SYNTAX: FORMAT [-delete | -reset] [] [ []] */ +static void cmd_format(const char *data) +{ + GHashTable *optlist; + GSList *tmp, *modules; + char *module, *key, *value; + void *free_arg; + int reset; + + if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS, + "format", &optlist, &module, &key, &value)) + return; + + modules = get_sorted_modules(); + if (*module == '\0') + module = NULL; + else if (theme_search(modules, module) == NULL) { + /* first argument isn't module.. */ + cmd_params_free(free_arg); + if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS, + "format", &optlist, &key, &value)) + return; + module = NULL; + } + + reset = FALSE; + if (*key == '\0') key = NULL; + if (g_hash_table_lookup(optlist, "reset")) + reset = TRUE; + else if (g_hash_table_lookup(optlist, "delete")) + value = ""; + else if (*value == '\0') + value = NULL; + + for (tmp = modules; tmp != NULL; tmp = tmp->next) { + THEME_SEARCH_REC *rec = tmp->data; + + if (module == NULL || g_strcasecmp(rec->short_name, module) == 0) + theme_show(rec, key, value, reset); + } + g_slist_foreach(modules, (GFunc) g_free, NULL); + g_slist_free(modules); + + cmd_params_free(free_arg); +} + +static void module_save(const char *module, MODULE_THEME_REC *rec, + CONFIG_REC *config) +{ + CONFIG_NODE *fnode, *node; + FORMAT_REC *formats; + int n; + + formats = g_hash_table_lookup(default_formats, rec->name); + if (formats == NULL) return; + + fnode = config_node_traverse(config, "formats", TRUE); + + node = config_node_section(fnode, rec->name, NODE_TYPE_BLOCK); + for (n = 0; formats[n].def != NULL; n++) { + if (rec->formats[n] != NULL) { + config_node_set_str(config, node, formats[n].tag, + rec->formats[n]); + } + } + + if (node->value == NULL) { + /* not modified, don't keep the empty section */ + config_node_remove(config, fnode, node); + if (fnode->value == NULL) + config_node_remove(config, config->mainnode, fnode); + } +} + +static void theme_save(THEME_REC *theme) +{ + CONFIG_REC *config; + char *path; + int ok; + + config = config_open(theme->path, -1); + if (config != NULL) + config_parse(config); + else { + if (g_strcasecmp(theme->name, "default") == 0) { + config = config_open(NULL, -1); + config_parse_data(config, default_theme, "internal"); + config_change_file_name(config, theme->path, 0660); + } else { + config = config_open(theme->path, 0660); + if (config == NULL) + return; + config_parse(config); + } + } + + g_hash_table_foreach(theme->modules, (GHFunc) module_save, config); + + /* always save the theme to ~/.silc/ */ + path = g_strdup_printf("%s/.silc/%s", g_get_home_dir(), + g_basename(theme->path)); + ok = config_write(config, path, 0660) == 0; + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + ok ? TXT_THEME_SAVED : TXT_THEME_SAVE_FAILED, + path, config_last_error(config)); + + g_free(path); + config_close(config); +} + +/* save changed formats */ +static void cmd_save(void) +{ + GSList *tmp; + + for (tmp = themes; tmp != NULL; tmp = tmp->next) { + THEME_REC *theme = tmp->data; + + theme_save(theme); + } +} + +static void complete_format_list(THEME_SEARCH_REC *rec, const char *key, GList **list) +{ + FORMAT_REC *formats; + int n, len; + + formats = g_hash_table_lookup(default_formats, rec->name); + + len = strlen(key); + for (n = 1; formats[n].def != NULL; n++) { + const char *item = formats[n].tag; + + if (item != NULL && g_strncasecmp(item, key, len) == 0) + *list = g_list_append(*list, g_strdup(item)); + } +} + +static GList *completion_get_formats(const char *module, const char *key) +{ + GSList *modules, *tmp; + GList *list; + + g_return_val_if_fail(key != NULL, NULL); + + list = NULL; + + modules = get_sorted_modules(); + if (*module == '\0' || theme_search(modules, module) != NULL) { + for (tmp = modules; tmp != NULL; tmp = tmp->next) { + THEME_SEARCH_REC *rec = tmp->data; + + if (*module == '\0' || g_strcasecmp(rec->short_name, module) == 0) + complete_format_list(rec, key, &list); + } + } + g_slist_foreach(modules, (GFunc) g_free, NULL); + g_slist_free(modules); + + return list; +} + +static void sig_complete_format(GList **list, WINDOW_REC *window, + const char *word, const char *line, int *want_space) +{ + const char *ptr; + int words; + + g_return_if_fail(list != NULL); + g_return_if_fail(word != NULL); + g_return_if_fail(line != NULL); + + ptr = line; + + words = 0; + do { + words++; + ptr = strchr(ptr, ' '); + } while (ptr != NULL); + + if (words > 2) + return; + + *list = completion_get_formats(line, word); + if (*list != NULL) signal_stop(); +} + +static void change_theme(const char *name, int verbose) +{ + THEME_REC *rec; + + rec = theme_load(name); + if (rec != NULL) { + current_theme = rec; + if (verbose) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_THEME_CHANGED, + rec->name, rec->path); + } + } else if (verbose) { + printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, + TXT_THEME_NOT_FOUND, name); + } +} + +static void read_settings(void) +{ + const char *theme; + + theme = settings_get_str("theme"); + if (strcmp(current_theme->name, theme) != 0) + change_theme(theme, TRUE); +} + +static void themes_read(void) +{ + char *fname; + + while (themes != NULL) + theme_destroy(themes->data); + + /* first there's default theme.. */ + current_theme = theme_load("default"); + if (current_theme == NULL) { + fname = g_strdup_printf("%s/.silc/default.theme", + g_get_home_dir()); + current_theme = theme_create(fname, "default"); + current_theme->default_color = 0; + current_theme->default_real_color = 7; + theme_read(current_theme, NULL, default_theme); + g_free(fname); + } + + window_themes_update(); + change_theme(settings_get_str("theme"), FALSE); +} + +void themes_init(void) +{ + settings_add_str("lookandfeel", "theme", "default"); + + default_formats = g_hash_table_new((GHashFunc) g_str_hash, + (GCompareFunc) g_str_equal); + + init_finished = FALSE; + init_errors = NULL; + + themes = NULL; + themes_read(); + + command_bind("format", NULL, (SIGNAL_FUNC) cmd_format); + command_bind("save", NULL, (SIGNAL_FUNC) cmd_save); + signal_add("complete command format", (SIGNAL_FUNC) sig_complete_format); + signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_errors); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + signal_add("setup reread", (SIGNAL_FUNC) themes_read); + + command_set_options("format", "delete reset"); +} + +void themes_deinit(void) +{ + while (themes != NULL) + theme_destroy(themes->data); + + g_hash_table_destroy(default_formats); + default_formats = NULL; + + command_unbind("format", (SIGNAL_FUNC) cmd_format); + command_unbind("save", (SIGNAL_FUNC) cmd_save); + signal_remove("complete command format", (SIGNAL_FUNC) sig_complete_format); + signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_errors); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); + signal_remove("setup reread", (SIGNAL_FUNC) themes_read); +} diff --git a/apps/irssi/src/fe-common/core/themes.h b/apps/irssi/src/fe-common/core/themes.h new file mode 100644 index 00000000..96f23400 --- /dev/null +++ b/apps/irssi/src/fe-common/core/themes.h @@ -0,0 +1,65 @@ +#ifndef __THEMES_H +#define __THEMES_H + +typedef struct { + char *name; + + int count; + char **formats; /* in same order as in module's default formats */ + char **expanded_formats; /* this contains the formats after + expanding {templates} */ +} MODULE_THEME_REC; + +typedef struct { + char *path; + char *name; + time_t last_modify; + + int default_color; /* default color to use with text with default + background. default is 0 which means the default + color set by terminal */ + int default_real_color; /* default color to use with background set. + this shouldn't be 0, unless black is really + wanted. default is 7 (white). */ + GHashTable *modules; + + int replace_keys[256]; /* index to replace_values for each char */ + GSList *replace_values; + GHashTable *abstracts; + + void *gui_data; +} THEME_REC; + +typedef struct _FORMAT_REC FORMAT_REC; + +extern GSList *themes; +extern THEME_REC *current_theme; +extern GHashTable *default_formats; + +THEME_REC *theme_create(const char *path, const char *name); +void theme_destroy(THEME_REC *rec); + +THEME_REC *theme_load(const char *name); + +#define theme_register(formats) theme_register_module(MODULE_NAME, formats) +#define theme_unregister() theme_unregister_module(MODULE_NAME) +void theme_register_module(const char *module, FORMAT_REC *formats); +void theme_unregister_module(const char *module); + +#define EXPAND_FLAG_IGNORE_REPLACES 0x01 /* don't use the character replaces when expanding */ +#define EXPAND_FLAG_IGNORE_EMPTY 0x02 /* if abstract's argument is empty, don't try to expand it (ie. {xx }, but not {xx}) */ +#define EXPAND_FLAG_RECURSIVE_MASK 0x0f +/* private */ +#define EXPAND_FLAG_ROOT 0x10 +#define EXPAND_FLAG_LASTCOLOR_ARG 0x20 + +char *theme_format_expand(THEME_REC *theme, const char *format); +char *theme_format_expand_data(THEME_REC *theme, const char **format, + char default_fg, char default_bg, + char *save_last_fg, char *save_last_bg, + int flags); + +void themes_init(void); +void themes_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/translation.c b/apps/irssi/src/fe-common/core/translation.c new file mode 100644 index 00000000..2713cc73 --- /dev/null +++ b/apps/irssi/src/fe-common/core/translation.c @@ -0,0 +1,122 @@ +/* + translation.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "line-split.h" +#include "misc.h" +#include "settings.h" + +unsigned char translation_in[256], translation_out[256]; + +void translation_reset(void) +{ + int n; + + for (n = 0; n < 256; n++) + translation_in[n] = (unsigned char) n; + for (n = 0; n < 256; n++) + translation_out[n] = (unsigned char) n; +} + +void translate_output(char *text) +{ + while (*text != '\0') { + *text = (char) translation_out[(int) (unsigned char) *text]; + text++; + } +} + +#define gethex(a) \ + (isdigit(a) ? ((a)-'0') : (toupper(a)-'A'+10)) + +void translation_parse_line(const char *str, int *pos) +{ + const char *ptr; + int value; + + for (ptr = str; *ptr != '\0'; ptr++) { + if (ptr[0] != '0' || ptr[1] != 'x') + break; + ptr += 2; + + value = (gethex(ptr[0]) << 4) + gethex(ptr[1]); + if (*pos < 256) + translation_in[*pos] = (unsigned char) value; + else + translation_out[*pos-256] = (unsigned char) value; + (*pos)++; + + ptr += 2; + if (*ptr != ',') break; + } +} + +int translation_read(const char *file) +{ + char tmpbuf[1024], *str, *path; + LINEBUF_REC *buffer; + int f, pos, ret, recvlen; + + g_return_val_if_fail(file != NULL, FALSE); + + path = convert_home(file); + f = open(file, O_RDONLY); + g_free(path); + + if (f == -1) return FALSE; + + pos = 0; buffer = NULL; + while (pos < 512) { + recvlen = read(f, tmpbuf, sizeof(tmpbuf)); + + ret = line_split(tmpbuf, recvlen, &str, &buffer); + if (ret <= 0) break; + + translation_parse_line(str, &pos); + } + line_split_free(buffer); + + close(f); + if (pos != 512) + translation_reset(); + return pos == 512; +} + +static void read_settings(void) +{ + translation_read(settings_get_str("translation")); +} + +void translation_init(void) +{ + translation_reset(); + + settings_add_str("misc", "translation", ""); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); + + read_settings(); +} + +void translation_deinit(void) +{ + read_settings(); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/fe-common/core/translation.h b/apps/irssi/src/fe-common/core/translation.h new file mode 100644 index 00000000..48b2c3dc --- /dev/null +++ b/apps/irssi/src/fe-common/core/translation.h @@ -0,0 +1,12 @@ +#ifndef __TRANSLATION_H +#define __TRANSLATION_H + +extern unsigned char translation_in[256], translation_out[256]; + +int translation_read(const char *file); +void translate_output(char *text); + +void translation_init(void); +void translation_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/window-activity.c b/apps/irssi/src/fe-common/core/window-activity.c new file mode 100644 index 00000000..3e4cd80c --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-activity.c @@ -0,0 +1,159 @@ +/* + window-activity.c : irssi + + Copyright (C) 1999-2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "levels.h" +#include "servers.h" +#include "channels.h" +#include "misc.h" +#include "settings.h" + +#include "fe-windows.h" +#include "window-items.h" +#include "nicklist.h" +#include "hilight-text.h" +#include "formats.h" + +static char **hide_targets; +static int hide_level, msg_level, hilight_level; + +static void window_activity(WINDOW_REC *window, int data_level, + const char *hilight_color) +{ + int old_data_level; + + old_data_level = window->data_level; + if (data_level == 0 || window->data_level < data_level) { + window->data_level = data_level; + g_free_not_null(window->hilight_color); + window->hilight_color = g_strdup(hilight_color); + signal_emit("window hilight", 1, window); + } + + signal_emit("window activity", 2, window, + GINT_TO_POINTER(old_data_level)); +} + +static void window_item_activity(WI_ITEM_REC *item, int data_level, + const char *hilight_color) +{ + int old_data_level; + + old_data_level = item->data_level; + if (data_level == 0 || item->data_level < data_level) { + item->data_level = data_level; + g_free_not_null(item->hilight_color); + item->hilight_color = g_strdup(hilight_color); + signal_emit("window item hilight", 1, item); + } + + signal_emit("window item activity", 2, item, + GINT_TO_POINTER(old_data_level)); +} + +#define hide_target_activity(data_level, target) \ + ((target) != NULL && hide_targets != NULL && \ + strarray_find(hide_targets, target) != -1) + +static void sig_hilight_text(TEXT_DEST_REC *dest, const char *msg) +{ + WI_ITEM_REC *item; + int data_level; + + if (dest->window == active_win || (dest->level & hide_level)) + return; + + if (dest->level & hilight_level) { + data_level = DATA_LEVEL_HILIGHT+dest->hilight_priority; + } else { + data_level = (dest->level & msg_level) ? + DATA_LEVEL_MSG : DATA_LEVEL_TEXT; + } + + if ((dest->level & MSGLEVEL_HILIGHT) == 0 && + hide_target_activity(data_level, dest->target)) + return; + + if (dest->target != NULL) { + item = window_item_find(dest->server, dest->target); + if (item != NULL) { + window_item_activity(item, data_level, + dest->hilight_color); + } + } + window_activity(dest->window, data_level, dest->hilight_color); +} + +static void sig_dehilight_window(WINDOW_REC *window) +{ + GSList *tmp; + + g_return_if_fail(window != NULL); + + if (window->data_level != 0) { + window_activity(window, 0, NULL); + for (tmp = window->items; tmp != NULL; tmp = tmp->next) + window_item_activity(tmp->data, 0, NULL); + } +} + +static void read_settings(void) +{ + const char *targets; + + if (hide_targets != NULL) + g_strfreev(hide_targets); + + targets = settings_get_str("activity_hide_targets"); + hide_targets = *targets == '\0' ? NULL : + g_strsplit(targets, " ", -1); + + hide_level = MSGLEVEL_NEVER | MSGLEVEL_NO_ACT | + level2bits(settings_get_str("activity_hide_level")); + msg_level = level2bits(settings_get_str("activity_msg_level")); + hilight_level = MSGLEVEL_HILIGHT | + level2bits(settings_get_str("activity_hilight_level")); +} + +void window_activity_init(void) +{ + settings_add_str("lookandfeel", "activity_hide_targets", ""); + settings_add_str("lookandfeel", "activity_hide_level", ""); + settings_add_str("lookandfeel", "activity_msg_level", "PUBLIC"); + settings_add_str("lookandfeel", "activity_hilight_level", "MSGS DCCMSGS"); + + read_settings(); + signal_add("print text", (SIGNAL_FUNC) sig_hilight_text); + signal_add("window changed", (SIGNAL_FUNC) sig_dehilight_window); + signal_add("window dehilight", (SIGNAL_FUNC) sig_dehilight_window); + signal_add("setup changed", (SIGNAL_FUNC) read_settings); +} + +void window_activity_deinit(void) +{ + if (hide_targets != NULL) + g_strfreev(hide_targets); + + signal_remove("print text", (SIGNAL_FUNC) sig_hilight_text); + signal_remove("window changed", (SIGNAL_FUNC) sig_dehilight_window); + signal_remove("window dehilight", (SIGNAL_FUNC) sig_dehilight_window); + signal_remove("setup changed", (SIGNAL_FUNC) read_settings); +} diff --git a/apps/irssi/src/fe-common/core/window-commands.c b/apps/irssi/src/fe-common/core/window-commands.c new file mode 100644 index 00000000..17cf65e5 --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-commands.c @@ -0,0 +1,572 @@ +/* + window-commands.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "signals.h" +#include "commands.h" +#include "misc.h" +#include "servers.h" + +#include "levels.h" + +#include "themes.h" +#include "fe-windows.h" +#include "window-items.h" +#include "windows-layout.h" +#include "printtext.h" + +static void cmd_window(const char *data, void *server, WI_ITEM_REC *item) +{ + if (is_numeric(data, 0)) { + signal_emit("command window refnum", 3, data, server, item); + return; + } + + command_runsub("window", data, server, item); +} + +/* SYNTAX: WINDOW NEW [hide] */ +static void cmd_window_new(const char *data, void *server, WI_ITEM_REC *item) +{ + WINDOW_REC *window; + int type; + + g_return_if_fail(data != NULL); + + type = (g_strncasecmp(data, "hid", 3) == 0 || g_strcasecmp(data, "tab") == 0) ? 1 : + (g_strcasecmp(data, "split") == 0 ? 2 : 0); + signal_emit("gui window create override", 1, GINT_TO_POINTER(type)); + + window = window_create(NULL, FALSE); + window_change_server(window, server); +} + +/* SYNTAX: WINDOW CLOSE [ [] */ +static void cmd_window_close(const char *data) +{ + GSList *tmp, *destroys; + char *first, *last; + int first_num, last_num; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 2, &first, &last)) + return; + + if ((*first != '\0' && !is_numeric(first, '\0')) || + ((*last != '\0') && !is_numeric(last, '\0'))) { + cmd_params_free(free_arg); + return; + } + + first_num = *first == '\0' ? active_win->refnum : atoi(first); + last_num = *last == '\0' ? active_win->refnum : atoi(last); + + /* get list of windows to destroy */ + destroys = NULL; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + if (rec->refnum >= first_num && rec->refnum <= last_num) + destroys = g_slist_append(destroys, rec); + } + + /* really destroy the windows */ + while (destroys != NULL) { + WINDOW_REC *rec = destroys->data; + + if (windows->next != NULL) + window_destroy(rec); + + destroys = g_slist_remove(destroys, rec); + } + + cmd_params_free(free_arg); +} + +/* SYNTAX: WINDOW REFNUM */ +static void cmd_window_refnum(const char *data) +{ + WINDOW_REC *window; + + if (!is_numeric(data, 0)) + return; + + window = window_find_refnum(atoi(data)); + if (window != NULL) + window_set_active(window); +} + +/* return the first window number with the highest activity */ +static WINDOW_REC *window_highest_activity(WINDOW_REC *window) +{ + WINDOW_REC *rec, *max_win; + GSList *tmp; + int max_act, through; + + g_return_val_if_fail(window != NULL, NULL); + + max_win = NULL; max_act = 0; through = FALSE; + + tmp = g_slist_find(windows, window); + for (;; tmp = tmp->next) { + if (tmp == NULL) { + tmp = windows; + through = TRUE; + } + + if (through && tmp->data == window) + break; + + rec = tmp->data; + + if (rec->data_level > 0 && max_act < rec->data_level) { + max_act = rec->data_level; + max_win = rec; + } + } + + return max_win; +} + +/* SYNTAX: WINDOW GOTO active|| */ +static void cmd_window_goto(const char *data) +{ + WINDOW_REC *window; + + g_return_if_fail(data != NULL); + + if (is_numeric(data, 0)) { + cmd_window_refnum(data); + return; + } + + if (g_strcasecmp(data, "active") == 0) + window = window_highest_activity(active_win); + else + window = window_find_item(active_win->active_server, data); + + if (window != NULL) + window_set_active(window); +} + +/* SYNTAX: WINDOW NEXT */ +static void cmd_window_next(void) +{ + int num; + + num = window_refnum_next(active_win->refnum, TRUE); + if (num < 1) num = windows_refnum_last(); + + window_set_active(window_find_refnum(num)); +} + +/* SYNTAX: WINDOW LAST */ +static void cmd_window_last(void) +{ + if (windows->next != NULL) + window_set_active(windows->next->data); +} + +/* SYNTAX: WINDOW PREVIOUS */ +static void cmd_window_previous(void) +{ + int num; + + num = window_refnum_prev(active_win->refnum, TRUE); + if (num < 1) num = window_refnum_next(0, TRUE); + + window_set_active(window_find_refnum(num)); +} + +/* SYNTAX: WINDOW LEVEL [] */ +static void cmd_window_level(const char *data) +{ + char *level; + + g_return_if_fail(data != NULL); + + window_set_level(active_win, combine_level(active_win->level, data)); + + level = active_win->level == 0 ? g_strdup("NONE") : + bits2level(active_win->level); + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_WINDOW_LEVEL, level); + g_free(level); +} + +/* SYNTAX: WINDOW SERVER [-sticky | -unsticky] */ +static void cmd_window_server(const char *data) +{ + GHashTable *optlist; + SERVER_REC *server; + char *tag; + void *free_arg; + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS, + "window server", &optlist, &tag)) + return; + + if (*tag == '\0' && + (g_hash_table_lookup(optlist, "sticky") != NULL || + g_hash_table_lookup(optlist, "unsticky") != NULL)) { + tag = active_win->active_server->tag; + } + + if (*tag == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + server = server_find_tag(tag); + + if (g_hash_table_lookup(optlist, "unsticky") != NULL && + active_win->servertag != NULL) { + g_free_and_null(active_win->servertag); + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_UNSET_SERVER_STICKY, server->tag); + } + + if (active_win->servertag != NULL && + g_hash_table_lookup(optlist, "sticky") == NULL) { + printformat_window(active_win, MSGLEVEL_CLIENTERROR, + TXT_ERROR_SERVER_STICKY); + } else if (server == NULL) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_UNKNOWN_SERVER_TAG, tag); + } else if (active_win->active == NULL) { + window_change_server(active_win, server); + if (g_hash_table_lookup(optlist, "sticky") != NULL) { + g_free_not_null(active_win->servertag); + active_win->servertag = g_strdup(server->tag); + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_SET_SERVER_STICKY, server->tag); + } + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_SERVER_CHANGED, + server->tag, server->connrec->address, + server->connrec->chatnet == NULL ? "" : + server->connrec->chatnet); + } + + cmd_params_free(free_arg); +} + +static void cmd_window_item(const char *data, void *server, WI_ITEM_REC *item) +{ + command_runsub("window item", data, server, item); +} + +/* SYNTAX: WINDOW ITEM PREV */ +static void cmd_window_item_prev(void) +{ + window_item_prev(active_win); +} + +/* SYNTAX: WINDOW ITEM NEXT */ +static void cmd_window_item_next(void) +{ + window_item_next(active_win); +} + +/* SYNTAX: WINDOW ITEM GOTO */ +static void cmd_window_item_goto(const char *data, SERVER_REC *server) +{ + WI_ITEM_REC *item; + + item = window_item_find_window(active_win, server, data); + if (item != NULL) + window_item_set_active(active_win, item); +} + +/* SYNTAX: WINDOW ITEM MOVE | */ +static void cmd_window_item_move(const char *data, SERVER_REC *server, + WI_ITEM_REC *item) +{ + WINDOW_REC *window; + + if (is_numeric(data, '\0')) { + /* move current window item to specified window */ + window = window_find_refnum(atoi(data)); + } else { + /* move specified window item to current window */ + item = window_item_find(server, data); + window = active_win; + } + if (window != NULL && item != NULL) + window_item_set_active(window, item); +} + +/* SYNTAX: WINDOW NUMBER [-sticky] */ +static void cmd_window_number(const char *data) +{ + GHashTable *optlist; + char *refnum; + void *free_arg; + int num; + + if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS, + "window number", &optlist, &refnum)) + return; + + if (*refnum == '\0') + cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); + + num = atoi(refnum); + if (num < 1) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_REFNUM_TOO_LOW); + } else { + window_set_refnum(active_win, num); + active_win->sticky_refnum = + g_hash_table_lookup(optlist, "sticky") != NULL; + } + + cmd_params_free(free_arg); +} + +/* SYNTAX: WINDOW NAME */ +static void cmd_window_name(const char *data) +{ + window_set_name(active_win, data); +} + +/* we're moving the first window to last - move the first contiguous block + of refnums to left. Like if there's windows 1..5 and 7..10, move 1 to + 11, 2..5 to 1..4 and leave 7..10 alone */ +static void windows_move_left(WINDOW_REC *move_window) +{ + WINDOW_REC *window; + int refnum; + + window_set_refnum(move_window, windows_refnum_last()+1); + for (refnum = 2;; refnum++) { + window = window_find_refnum(refnum); + if (window == NULL) break; + + window_set_refnum(window, refnum-1); + } +} + +/* we're moving the last window to first - make some space so we can use the + refnum 1 */ +static void windows_move_right(WINDOW_REC *move_window) +{ + WINDOW_REC *window; + int refnum; + + /* find the first unused refnum, like if there's windows + 1..5 and 7..10, we only need to move 1..5 to 2..6 */ + refnum = 1; + while (window_find_refnum(refnum) != NULL) refnum++; + + refnum--; + while (refnum > 0) { + window = window_find_refnum(refnum); + g_return_if_fail(window != NULL); + window_set_refnum(window, window == move_window ? 1 : refnum+1); + + refnum--; + } +} + +static void cmd_window_move_left(void) +{ + int refnum; + + refnum = window_refnum_prev(active_win->refnum, TRUE); + if (refnum != -1) { + window_set_refnum(active_win, refnum); + return; + } + + windows_move_left(active_win); +} + +static void cmd_window_move_right(void) +{ + int refnum; + + refnum = window_refnum_next(active_win->refnum, TRUE); + if (refnum != -1) { + window_set_refnum(active_win, refnum); + return; + } + + windows_move_right(active_win); +} + +/* SYNTAX: WINDOW MOVE |left|right */ +static void cmd_window_move(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + int new_refnum, refnum; + + if (!is_numeric(data, 0)) { + command_runsub("window move", data, server, item); + return; + } + + new_refnum = atoi(data); + if (new_refnum > active_win->refnum) { + for (;;) { + refnum = window_refnum_next(active_win->refnum, FALSE); + if (refnum == -1 || refnum > new_refnum) + break; + + window_set_refnum(active_win, refnum); + } + } else { + for (;;) { + refnum = window_refnum_prev(active_win->refnum, FALSE); + if (refnum == -1 || refnum < new_refnum) + break; + + window_set_refnum(active_win, refnum); + } + } +} + +/* SYNTAX: WINDOW LIST */ +static void cmd_window_list(void) +{ + GSList *tmp, *sorted; + char *levelstr; + + sorted = windows_get_sorted(); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_HEADER); + for (tmp = sorted; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + levelstr = bits2level(rec->level); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_LINE, + rec->refnum, rec->name == NULL ? "" : rec->name, + rec->active == NULL ? "" : rec->active->name, + rec->active_server == NULL ? "" : ((SERVER_REC *) rec->active_server)->tag, + levelstr); + g_free(levelstr); + } + g_slist_free(sorted); + printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_WINDOWLIST_FOOTER); +} + +/* SYNTAX: WINDOW THEME */ +static void cmd_window_theme(const char *data) +{ + THEME_REC *theme; + + g_free_not_null(active_win->theme_name); + active_win->theme_name = g_strdup(data); + + active_win->theme = theme = theme_load(data); + if (theme != NULL) { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_WINDOW_THEME_CHANGED, + theme->name, theme->path); + } else { + printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, + TXT_THEME_NOT_FOUND, data); + } +} + +static void cmd_layout(const char *data, SERVER_REC *server, WI_ITEM_REC *item) +{ + command_runsub("layout", data, server, item); +} + +/* SYNTAX: FOREACH WINDOW */ +static void cmd_foreach_window(const char *data) +{ + WINDOW_REC *old; + GSList *tmp; + + old = active_win; + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + active_win = rec; + signal_emit("send command", 3, data, rec->active_server, + rec->active); + } + active_win = old; +} + +void window_commands_init(void) +{ + command_bind("window", NULL, (SIGNAL_FUNC) cmd_window); + command_bind("window new", NULL, (SIGNAL_FUNC) cmd_window_new); + command_bind("window close", NULL, (SIGNAL_FUNC) cmd_window_close); + command_bind("window kill", NULL, (SIGNAL_FUNC) cmd_window_close); + command_bind("window server", NULL, (SIGNAL_FUNC) cmd_window_server); + command_bind("window refnum", NULL, (SIGNAL_FUNC) cmd_window_refnum); + command_bind("window goto", NULL, (SIGNAL_FUNC) cmd_window_goto); + command_bind("window previous", NULL, (SIGNAL_FUNC) cmd_window_previous); + command_bind("window next", NULL, (SIGNAL_FUNC) cmd_window_next); + command_bind("window last", NULL, (SIGNAL_FUNC) cmd_window_last); + command_bind("window level", NULL, (SIGNAL_FUNC) cmd_window_level); + command_bind("window item", NULL, (SIGNAL_FUNC) cmd_window_item); + command_bind("window item prev", NULL, (SIGNAL_FUNC) cmd_window_item_prev); + command_bind("window item next", NULL, (SIGNAL_FUNC) cmd_window_item_next); + command_bind("window item goto", NULL, (SIGNAL_FUNC) cmd_window_item_goto); + command_bind("window item move", NULL, (SIGNAL_FUNC) cmd_window_item_move); + command_bind("window number", NULL, (SIGNAL_FUNC) cmd_window_number); + command_bind("window name", NULL, (SIGNAL_FUNC) cmd_window_name); + command_bind("window move", NULL, (SIGNAL_FUNC) cmd_window_move); + command_bind("window move left", NULL, (SIGNAL_FUNC) cmd_window_move_left); + command_bind("window move right", NULL, (SIGNAL_FUNC) cmd_window_move_right); + command_bind("window list", NULL, (SIGNAL_FUNC) cmd_window_list); + command_bind("window theme", NULL, (SIGNAL_FUNC) cmd_window_theme); + command_bind("layout", NULL, (SIGNAL_FUNC) cmd_layout); + /* SYNTAX: LAYOUT SAVE */ + command_bind("layout save", NULL, (SIGNAL_FUNC) windows_layout_save); + /* SYNTAX: LAYOUT RESET */ + command_bind("layout reset", NULL, (SIGNAL_FUNC) windows_layout_reset); + command_bind("foreach window", NULL, (SIGNAL_FUNC) cmd_foreach_window); + + command_set_options("window number", "sticky"); + command_set_options("window server", "sticky unsticky"); +} + +void window_commands_deinit(void) +{ + command_unbind("window", (SIGNAL_FUNC) cmd_window); + command_unbind("window new", (SIGNAL_FUNC) cmd_window_new); + command_unbind("window close", (SIGNAL_FUNC) cmd_window_close); + command_unbind("window kill", (SIGNAL_FUNC) cmd_window_close); + command_unbind("window server", (SIGNAL_FUNC) cmd_window_server); + command_unbind("window refnum", (SIGNAL_FUNC) cmd_window_refnum); + command_unbind("window goto", (SIGNAL_FUNC) cmd_window_goto); + command_unbind("window previous", (SIGNAL_FUNC) cmd_window_previous); + command_unbind("window next", (SIGNAL_FUNC) cmd_window_next); + command_unbind("window last", (SIGNAL_FUNC) cmd_window_last); + command_unbind("window level", (SIGNAL_FUNC) cmd_window_level); + command_unbind("window item", (SIGNAL_FUNC) cmd_window_item); + command_unbind("window item prev", (SIGNAL_FUNC) cmd_window_item_prev); + command_unbind("window item next", (SIGNAL_FUNC) cmd_window_item_next); + command_unbind("window item goto", (SIGNAL_FUNC) cmd_window_item_goto); + command_unbind("window item move", (SIGNAL_FUNC) cmd_window_item_move); + command_unbind("window number", (SIGNAL_FUNC) cmd_window_number); + command_unbind("window name", (SIGNAL_FUNC) cmd_window_name); + command_unbind("window move", (SIGNAL_FUNC) cmd_window_move); + command_unbind("window move left", (SIGNAL_FUNC) cmd_window_move_left); + command_unbind("window move right", (SIGNAL_FUNC) cmd_window_move_right); + command_unbind("window list", (SIGNAL_FUNC) cmd_window_list); + command_unbind("window theme", (SIGNAL_FUNC) cmd_window_theme); + command_unbind("layout", (SIGNAL_FUNC) cmd_layout); + command_unbind("layout save", (SIGNAL_FUNC) windows_layout_save); + command_unbind("layout reset", (SIGNAL_FUNC) windows_layout_reset); + command_unbind("foreach window", (SIGNAL_FUNC) cmd_foreach_window); +} diff --git a/apps/irssi/src/fe-common/core/window-items.c b/apps/irssi/src/fe-common/core/window-items.c new file mode 100644 index 00000000..d7726521 --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-items.c @@ -0,0 +1,326 @@ +/* + window-items.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "module-formats.h" +#include "modules.h" +#include "signals.h" +#include "servers.h" +#include "settings.h" + +#include "levels.h" + +#include "fe-windows.h" +#include "window-items.h" +#include "printtext.h" + +void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic) +{ + g_return_if_fail(window != NULL); + g_return_if_fail(item != NULL); + + item->window = window; + + if (window->items == NULL) { + window->active = item; + window->active_server = item->server; + } + + if (!automatic || settings_get_bool("window_auto_change")) { + if (automatic) + signal_emit("window changed automatic", 1, window); + window_set_active(window); + } + + window->items = g_slist_append(window->items, item); + signal_emit("window item new", 2, window, item); + + if (!automatic || g_slist_length(window->items) == 1) { + window->active = NULL; + window_item_set_active(window, item); + } +} + +void window_item_remove(WI_ITEM_REC *item) +{ + WINDOW_REC *window; + + g_return_if_fail(item != NULL); + + window = window_item_window(item); + + if (g_slist_find(window->items, item) == NULL) + return; + + item->window = NULL; + window->items = g_slist_remove(window->items, item); + + if (window->active == item) { + window_item_set_active(window, window->items == NULL ? NULL : + window->items->data); + } + + signal_emit("window item remove", 2, window, item); +} + +void window_item_destroy(WI_ITEM_REC *item) +{ + WINDOW_REC *window; + + window = window_item_window(item); + window_item_remove(item); + + signal_emit("window item destroy", 2, window, item); +} + +void window_item_change_server(WI_ITEM_REC *item, void *server) +{ + WINDOW_REC *window; + + g_return_if_fail(item != NULL); + + window = window_item_window(item); + item->server = server; + + signal_emit("window item server changed", 2, window, item); + if (window->active == item) window_change_server(window, item->server); +} + +void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(window != NULL); + + if (item != NULL && window_item_window(item) != window) { + /* move item to different window */ + window_item_remove(item); + window_item_add(window, item, FALSE); + } + + if (window->active != item) { + window->active = item; + if (item != NULL && window->active_server != item->server) + window_change_server(window, item->server); + signal_emit("window item changed", 2, window, item); + } +} + +/* Return TRUE if `item' is the active window item in the window. + `item' can be NULL. */ +int window_item_is_active(WI_ITEM_REC *item) +{ + WINDOW_REC *window; + + if (item == NULL) + return FALSE; + + window = window_item_window(item); + if (window == NULL) + return FALSE; + + return window->active == item; +} + +void window_item_prev(WINDOW_REC *window) +{ + WI_ITEM_REC *last; + GSList *tmp; + + g_return_if_fail(window != NULL); + + last = NULL; + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + WI_ITEM_REC *rec = tmp->data; + + if (rec != window->active) + last = rec; + else { + /* current channel. did we find anything? + if not, go to the last channel */ + if (last != NULL) break; + } + } + + if (last != NULL) + window_item_set_active(window, last); +} + +void window_item_next(WINDOW_REC *window) +{ + WI_ITEM_REC *next; + GSList *tmp; + int gone; + + g_return_if_fail(window != NULL); + + next = NULL; gone = FALSE; + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + WI_ITEM_REC *rec = tmp->data; + + if (rec == window->active) + gone = TRUE; + else { + if (gone) { + /* found the next channel */ + next = rec; + break; + } + + if (next == NULL) + next = rec; /* fallback to first channel */ + } + } + + if (next != NULL) + window_item_set_active(window, next); +} + +WI_ITEM_REC *window_item_find_window(WINDOW_REC *window, + void *server, const char *name) +{ + GSList *tmp; + + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + WI_ITEM_REC *rec = tmp->data; + + if ((server == NULL || rec->server == server) && + g_strcasecmp(name, rec->name) == 0) return rec; + } + + return NULL; +} + +/* Find wanted window item by name. `server' can be NULL. */ +WI_ITEM_REC *window_item_find(void *server, const char *name) +{ + WI_ITEM_REC *item; + GSList *tmp; + + g_return_val_if_fail(name != NULL, NULL); + + for (tmp = windows; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + item = window_item_find_window(rec, server, name); + if (item != NULL) return item; + } + + return NULL; +} + +static int window_bind_has_sticky(WINDOW_REC *window) +{ + GSList *tmp; + + for (tmp = window->bound_items; tmp != NULL; tmp = tmp->next) { + WINDOW_BIND_REC *rec = tmp->data; + + if (rec->sticky) + return TRUE; + } + + return FALSE; +} + +void window_item_create(WI_ITEM_REC *item, int automatic) +{ + WINDOW_REC *window; + GSList *tmp, *sorted; + int clear_waiting, reuse_unused_windows; + + g_return_if_fail(item != NULL); + + reuse_unused_windows = + !settings_get_bool("autoclose_windows") || + settings_get_bool("reuse_unused_windows"); + + clear_waiting = TRUE; + window = NULL; + sorted = windows_get_sorted(); + for (tmp = sorted; tmp != NULL; tmp = tmp->next) { + WINDOW_REC *rec = tmp->data; + + /* is item bound to this window? */ + if (item->server != NULL && + window_bind_find(rec, item->server->tag, item->name)) { + window = rec; + clear_waiting = FALSE; + break; + } + + /* use this window IF: + - reuse_unused_windows is ON + - window has no existing items + - window has no name + - window has no sticky binds (/LAYOUT SAVEd) + - we already haven't found "good enough" window, + except if + - this is the active window + - old window had some temporary bounds and this + one doesn't + */ + if (reuse_unused_windows && rec->items == NULL && + rec->name == NULL && !window_bind_has_sticky(rec) && + (window == NULL || rec == active_win || + window->bound_items != NULL)) + window = rec; + } + g_slist_free(sorted); + + if (window == NULL && !settings_get_bool("autocreate_windows")) { + /* never create new windows automatically */ + window = active_win; + } + + if (window == NULL) { + /* create new window to use */ + window = window_create(item, automatic); + } else { + /* use existing window */ + window_item_add(window, item, automatic); + } + + if (clear_waiting) + window_bind_remove_unsticky(window); +} + +static void signal_window_item_changed(WINDOW_REC *window, WI_ITEM_REC *item) +{ + g_return_if_fail(window != NULL); + + if (g_slist_length(window->items) > 1) { + /* default to printing "talking with ...", + you can override it it you wish */ + printformat(item->server, item->name, MSGLEVEL_CLIENTNOTICE, + TXT_TALKING_WITH, item->name); + } +} + +void window_items_init(void) +{ + settings_add_bool("lookandfeel", "reuse_unused_windows", FALSE); + settings_add_bool("lookandfeel", "autocreate_windows", TRUE); + + signal_add_last("window item changed", (SIGNAL_FUNC) signal_window_item_changed); +} + +void window_items_deinit(void) +{ + signal_remove("window item changed", (SIGNAL_FUNC) signal_window_item_changed); +} diff --git a/apps/irssi/src/fe-common/core/window-items.h b/apps/irssi/src/fe-common/core/window-items.h new file mode 100644 index 00000000..f8db3c39 --- /dev/null +++ b/apps/irssi/src/fe-common/core/window-items.h @@ -0,0 +1,34 @@ +#ifndef __WINDOW_ITEMS_H +#define __WINDOW_ITEMS_H + +#include "fe-windows.h" + +/* Add/remove/destroy window item from `window' */ +void window_item_add(WINDOW_REC *window, WI_ITEM_REC *item, int automatic); +void window_item_remove(WI_ITEM_REC *item); +void window_item_destroy(WI_ITEM_REC *item); + +/* Find a window for `item' and call window_item_add(). */ +void window_item_create(WI_ITEM_REC *item, int automatic); + +#define window_item_window(item) \ + ((WINDOW_REC *) ((WI_ITEM_REC *) (item))->window) +void window_item_change_server(WI_ITEM_REC *item, void *server); + +void window_item_set_active(WINDOW_REC *window, WI_ITEM_REC *item); +/* Return TRUE if `item' is the active window item in the window. + `item' can be NULL. */ +int window_item_is_active(WI_ITEM_REC *item); + +void window_item_prev(WINDOW_REC *window); +void window_item_next(WINDOW_REC *window); + +/* Find wanted window item by name. `server' can be NULL. */ +WI_ITEM_REC *window_item_find(void *server, const char *name); +WI_ITEM_REC *window_item_find_window(WINDOW_REC *window, + void *server, const char *name); + +void window_items_init(void); +void window_items_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/core/windows-layout.c b/apps/irssi/src/fe-common/core/windows-layout.c new file mode 100644 index 00000000..814127fb --- /dev/null +++ b/apps/irssi/src/fe-common/core/windows-layout.c @@ -0,0 +1,196 @@ +/* + windows-layout.c : irssi + + Copyright (C) 2000-2001 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "levels.h" +#include "lib-config/iconfig.h" +#include "settings.h" + +#include "chat-protocols.h" +#include "servers.h" +#include "queries.h" + +#include "module-formats.h" +#include "printtext.h" +#include "themes.h" +#include "fe-windows.h" +#include "window-items.h" + +static void sig_window_restore_item(WINDOW_REC *window, const char *type, + CONFIG_NODE *node) +{ + char *name, *tag, *chat_type; + + chat_type = config_node_get_str(node, "chat_type", NULL); + name = config_node_get_str(node, "name", NULL); + tag = config_node_get_str(node, "tag", NULL); + + if (name == NULL || tag == NULL) + return; + + if (g_strcasecmp(type, "CHANNEL") == 0) { + /* bind channel to window */ + WINDOW_BIND_REC *rec = window_bind_add(window, tag, name); + rec->sticky = TRUE; + } else if (g_strcasecmp(type, "QUERY") == 0 && chat_type != NULL) { + /* create query immediately */ + chat_protocol_find(chat_type)->query_create(tag, name, TRUE); + } +} + +static void window_add_items(WINDOW_REC *window, CONFIG_NODE *node) +{ + GSList *tmp; + char *type; + + if (node == NULL) + return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + type = config_node_get_str(node, "type", NULL); + if (type != NULL) { + signal_emit("window restore item", 3, + window, type, node); + } + } +} + +void windows_layout_restore(void) +{ + WINDOW_REC *window; + CONFIG_NODE *node; + GSList *tmp; + + node = iconfig_node_traverse("windows", FALSE); + if (node == NULL) return; + + for (tmp = node->value; tmp != NULL; tmp = tmp->next) { + CONFIG_NODE *node = tmp->data; + + window = window_create(NULL, TRUE); + window_set_refnum(window, atoi(node->key)); + window->sticky_refnum = config_node_get_bool(node, "sticky_refnum", FALSE); + window_set_name(window, config_node_get_str(node, "name", NULL)); + window_set_level(window, level2bits(config_node_get_str(node, "level", ""))); + + window->servertag = g_strdup(config_node_get_str(node, "servertag", NULL)); + window->theme_name = g_strdup(config_node_get_str(node, "theme", NULL)); + if (window->theme_name != NULL) + window->theme = theme_load(window->theme_name); + + window_add_items(window, config_node_section(node, "items", -1)); + signal_emit("window restore", 2, window, node); + } + + signal_emit("windows restored", 0); +} + +static void window_save_items(WINDOW_REC *window, CONFIG_NODE *node) +{ + CONFIG_NODE *subnode; + GSList *tmp; + const char *type; + + node = config_node_section(node, "items", NODE_TYPE_LIST); + for (tmp = window->items; tmp != NULL; tmp = tmp->next) { + WI_ITEM_REC *rec = tmp->data; + SERVER_REC *server = rec->server; + + type = module_find_id_str("WINDOW ITEM TYPE", rec->type); + if (type == NULL) continue; + + subnode = config_node_section(node, NULL, NODE_TYPE_BLOCK); + + iconfig_node_set_str(subnode, "type", type); + type = chat_protocol_find_id(rec->chat_type)->name; + iconfig_node_set_str(subnode, "chat_type", type); + iconfig_node_set_str(subnode, "name", rec->name); + + if (server != NULL) + iconfig_node_set_str(subnode, "tag", server->tag); + else if (IS_QUERY(rec)) { + iconfig_node_set_str(subnode, "tag", + QUERY(rec)->server_tag); + } + } +} + +static void window_save(WINDOW_REC *window, CONFIG_NODE *node) +{ + char refnum[MAX_INT_STRLEN]; + + ltoa(refnum, window->refnum); + node = config_node_section(node, refnum, NODE_TYPE_BLOCK); + + if (window->sticky_refnum) + iconfig_node_set_bool(node, "sticky_refnum", TRUE); + + if (window->name != NULL) + iconfig_node_set_str(node, "name", window->name); + if (window->servertag != NULL) + iconfig_node_set_str(node, "servertag", window->servertag); + if (window->level != 0) { + char *level = bits2level(window->level); + iconfig_node_set_str(node, "level", level); + g_free(level); + } + if (window->theme_name != NULL) + iconfig_node_set_str(node, "theme", window->theme_name); + + if (window->items != NULL) + window_save_items(window, node); + + signal_emit("window save", 2, window, node); +} + +void windows_layout_save(void) +{ + CONFIG_NODE *node; + + iconfig_set_str(NULL, "windows", NULL); + node = iconfig_node_traverse("windows", TRUE); + + g_slist_foreach(windows, (GFunc) window_save, node); + signal_emit("windows saved", 0); + + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_WINDOWS_LAYOUT_SAVED); +} + +void windows_layout_reset(void) +{ + iconfig_set_str(NULL, "windows", NULL); + printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, + TXT_WINDOWS_LAYOUT_RESET); +} + +void windows_layout_init(void) +{ + signal_add("window restore item", (SIGNAL_FUNC) sig_window_restore_item); +} + +void windows_layout_deinit(void) +{ + signal_remove("window restore item", (SIGNAL_FUNC) sig_window_restore_item); +} diff --git a/apps/irssi/src/fe-common/core/windows-layout.h b/apps/irssi/src/fe-common/core/windows-layout.h new file mode 100644 index 00000000..d33fda52 --- /dev/null +++ b/apps/irssi/src/fe-common/core/windows-layout.h @@ -0,0 +1,11 @@ +#ifndef __WINDOWS_LAYOUT_H +#define __WINDOWS_LAYOUT_H + +void windows_layout_restore(void); +void windows_layout_save(void); +void windows_layout_reset(void); + +void windows_layout_init(void); +void windows_layout_deinit(void); + +#endif diff --git a/apps/irssi/src/fe-common/silc/Makefile.am b/apps/irssi/src/fe-common/silc/Makefile.am index 74efdc41..72abc05e 100644 --- a/apps/irssi/src/fe-common/silc/Makefile.am +++ b/apps/irssi/src/fe-common/silc/Makefile.am @@ -1,13 +1,13 @@ -INCLUDES = $(GLIB_CFLAGS) -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src - -SILC_INCLUDE=../../../.. IRSSI_INCLUDE=../../.. -INCLUDES = \ +include $(top_srcdir)/Makefile.defines.in + +ADD_INCLUDES = \ $(GLIB_CFLAGS) \ - -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DSYSCONFDIR=\""$(silc_etcdir)"\" \ -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src \ -I$(IRSSI_INCLUDE)/src/core \ + -I$(IRSSI_INCLUDE)/src/fe-common/core \ -I$(IRSSI_INCLUDE)/src/silc \ -I$(IRSSI_INCLUDE)/src/silc/core \ -I$(SILC_INCLUDE)/includes \ @@ -26,8 +26,10 @@ noinst_LIBRARIES = libfe_common_silc.a libfe_common_silc_a_SOURCES = \ fe-channels.c \ fe-common-silc.c \ + module-formats.c \ silc-modules.c noinst_HEADERS = \ + module-formats.h \ fe-common-silc.h \ module.h diff --git a/apps/irssi/src/fe-common/silc/fe-common-silc.c b/apps/irssi/src/fe-common/silc/fe-common-silc.c index c779caee..0d7b8cc7 100644 --- a/apps/irssi/src/fe-common/silc/fe-common-silc.c +++ b/apps/irssi/src/fe-common/silc/fe-common-silc.c @@ -1,25 +1,27 @@ /* - fe-common-silc.c : irssi - Copyright (C) 2000 Timo Sirainen + fe-common-silc.c - 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. + Author: Pekka Riikonen - 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. + Copyright (C) 2001 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "module.h" +#include "module-formats.h" #include "signals.h" +#include "themes.h" void fe_silc_channels_init(void); void fe_silc_channels_deinit(void); @@ -29,15 +31,18 @@ void fe_silc_modules_deinit(void); void fe_common_silc_init(void) { - fe_silc_channels_init(); - fe_silc_modules_init(); + theme_register(fecommon_silc_formats); + + fe_silc_channels_init(); + fe_silc_modules_init(); } void fe_common_silc_deinit(void) { - fe_silc_modules_deinit(); + fe_silc_modules_deinit(); + fe_silc_channels_deinit(); - fe_silc_channels_deinit(); + theme_unregister(); } void fe_common_silc_finish_init(void) diff --git a/apps/irssi/src/fe-common/silc/module-formats.c b/apps/irssi/src/fe-common/silc/module-formats.c index 2b30bffe..d2c62f23 100644 --- a/apps/irssi/src/fe-common/silc/module-formats.c +++ b/apps/irssi/src/fe-common/silc/module-formats.c @@ -1,211 +1,142 @@ /* - module-formats.c : irssi - Copyright (C) 2000 Timo Sirainen + modules-formats.c - 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. + Author: Pekka Riikonen - 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. + Copyright (C) 2001 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "module.h" +#include "fe-common/core/formats.h" #include "printtext.h" -FORMAT_REC fecommon_irc_formats[] = { - { MODULE_NAME, "IRC", 0 }, - - /* ---- */ - { NULL, "Server", 0 }, - - { "lag_disconnected", "No PONG reply from server %_$0%_ in $1 seconds, disconnecting", 2, { 0, 1 } }, - { "disconnected", "Disconnected from %_$0%_ %K[%n$1%K]", 2, { 0, 0 } }, - { "server_list", "%_$0%_: $1:$2 ($3)", 5, { 0, 0, 1, 0, 0 } }, - { "server_lookup_list", "%_$0%_: $1:$2 ($3) (connecting...)", 5, { 0, 0, 1, 0, 0 } }, - { "server_reconnect_list", "%_$0%_: $1:$2 ($3) ($5 left before reconnecting)", 6, { 0, 0, 1, 0, 0, 0 } }, - { "server_reconnect_removed", "Removed reconnection to server %_$0%_ port %_$1%_", 3, { 0, 1, 0 } }, - { "server_reconnect_not_found", "Reconnection tag %_$0%_ not found", 1, { 0 } }, - { "query_server_changed", "Query with %_$2%_ changed to server %_$1%_", 3, { 0, 0, 0 } }, - { "setupserver_added", "Server $0 saved", 2, { 0, 1 } }, - { "setupserver_removed", "Server $0 removed", 2, { 0, 1 } }, - { "setupserver_not_found", "Server $0 not found", 2, { 0, 1 } }, - { "setupserver_header", "Server Port IRC Net Settings", 0 }, - { "setupserver_line", "%|$[!20]0 $[5]1 $[10]2 $3", 4, { 0, 1, 0, 0 } }, - { "setupserver_footer", "", 0 }, - { "netsplit", "%RNetsplit%n %_$0%_ %_$1%_ quits: $2", 3, { 0, 0, 0 } }, - { "netsplit_more", "%RNetsplit%n %_$0%_ %_$1%_ quits: $2 (+$3 more, use /NETSPLIT to show all of them)", 4, { 0, 0, 0, 1 } }, - { "netsplit_join", "%CNetsplit%n over, joins: $0", 1, { 0 } }, - { "netsplit_join_more", "%CNetsplit%n over, joins: $0 (+$1 more)", 2, { 0, 1 } }, - { "no_netsplits", "There are no net splits", 0 }, - { "netsplits_header", "Nick Channel Server Splitted server", 0 }, - { "netsplits_line", "$[9]0 $[10]1 $[20]2 $3", 4, { 0, 0, 0, 0 } }, - { "netsplits_footer", "", 0 }, - { "ircnet_added", "Ircnet $0 saved", 1, { 0 } }, - { "ircnet_removed", "Ircnet $0 removed", 1, { 0 } }, - { "ircnet_not_found", "Ircnet $0 not found", 1, { 0 } }, - { "ircnet_header", "Ircnets:", 0 }, - { "ircnet_line", "$0: $1", 2, { 0, 0 } }, - { "ircnet_footer", "", 0 }, - - /* ---- */ - { NULL, "Channels", 0 }, - - { "join", "%c%_$0%_ %K[%c$1%K]%n has joined %_$2", 3, { 0, 0, 0 } }, - { "part", "%c$0 %K[%n$1%K]%n has left %_$2%_ %K[%n$3%K]", 4, { 0, 0, 0, 0 } }, - { "joinerror_toomany", "Cannot join to channel %_$0%_ %K(%nYou have joined to too many channels%K)", 1, { 0 } }, - { "joinerror_full", "Cannot join to channel %_$0%_ %K(%nChannel is full%K)", 1, { 0 } }, - { "joinerror_invite", "Cannot join to channel %_$0%_ %K(%nYou must be invited%K)", 1, { 0 } }, - { "joinerror_banned", "Cannot join to channel %_$0%_ %K(%nYou are banned%K)", 1, { 0 } }, - { "joinerror_bad_key", "Cannot join to channel %_$0%_ %K(%nBad channel key%K)", 1, { 0 } }, - { "joinerror_bad_mask", "Cannot join to channel %_$0%_ %K(%nBad channel mask%K)", 1, { 0 } }, - { "joinerror_unavail", "Cannot join to channel %_$0%_ %K(%nChannel is temporarily unavailable%K)", 1, { 0 } }, - { "kick", "%c$0%n was kicked from %_$1%_ by %_$2%_ %K[%n$3%K]", 4, { 0, 0, 0, 0 } }, - { "quit", "%c$0 %K[%n$1%K]%n has quit IRC %K[%n$2%K]", 3, { 0, 0, 0 } }, - { "quit_once", "%_$3%_ %c$0 %K[%n$1%K]%n has quit IRC %K[%n$2%K]", 4, { 0, 0, 0, 0 } }, - { "invite", "%_$0%_ invites you to %_$1", 2, { 0, 0 } }, - { "inviting", "Inviting $0 to %_$1", 2, { 0, 0 } }, - { "not_invited", "You have not been invited to a channel!", 0 }, - { "names", "%K[%g%_Users%_%K(%g$0%K)]%n $1", 2, { 0, 0 } }, - { "names_nick", "%K[%n%_$0%_$1%K] ", 2, { 0, 0 } }, - { "endofnames", "%g%_$0%_%K:%n Total of %_$1%_ nicks %K[%n%_$2%_ ops, %_$3%_ voices, %_$4%_ normal%K]", 5, { 0, 1, 1, 1, 1 } }, - { "channel_created", "Channel %_$0%_ created $1", 2, { 0, 0 } }, - { "topic", "Topic for %c$0%K:%n $1", 2, { 0, 0 } }, - { "no_topic", "No topic set for %c$0", 1, { 0 } }, - { "new_topic", "%_$0%_ changed the topic of %c$1%n to%K:%n $2", 3, { 0, 0, 0 } }, - { "topic_unset", "Topic unset by %_$0%_ on %c$1", 2, { 0, 0 } }, - { "topic_info", "Topic set by %_$0%_ %K[%n$1%K]", 2, { 0, 0 } }, - { "chanmode_change", "mode/%c$0 %K[%n$1%K]%n by %_$2", 3, { 0, 0, 0 } }, - { "server_chanmode_change", "%RServerMode/%c$0 %K[%n$1%K]%n by %_$2", 3, { 0, 0, 0 } }, - { "channel_mode", "mode/%c$0 %K[%n$1%K]", 2, { 0, 0 } }, - { "bantype", "Ban type changed to %_$0", 1, { 0 } }, - { "no_bans", "No bans in channel %_$0%_", 1, { 0 } }, - { "banlist", "%_$0%_: ban %c$1", 2, { 0, 0 } }, - { "banlist_long", "%_$0%_: ban %c$1 %K[%nby %_$2%_, $3 secs ago%K]", 4, { 0, 0, 0, 1 } }, - { "ebanlist", "%_$0%_: ban exception %c$1", 2, { 0, 0 } }, - { "ebanlist_long", "%_$0%_: ban exception %c$1 %K[%nby %_$2%_, $3 secs ago%K]", 4, { 0, 0, 0, 1 } }, - { "invitelist", "%_$0%_: invite %c$1", 2, { 0, 0 } }, - { "no_such_channel", "$0: No such channel", 1, { 0 } }, - { "channel_synced", "Join to %_$0%_ was synced in %_$1%_ secs", 2, { 0, 2 } }, - { "not_in_channels", "You are not on any channels", 0 }, - { "current_channel", "Current channel $0", 1, { 0 } }, - { "chanlist_header", "You are on the following channels:", 0 }, - { "chanlist_line", "$[-10]0 %|+$1 ($2): $3", 4, { 0, 0, 0, 0 } }, - { "chansetup_not_found", "Channel $0 not found", 2, { 0, 0 } }, - { "chansetup_added", "Channel $0 saved", 2, { 0, 0 } }, - { "chansetup_removed", "Channel $0 removed", 2, { 0, 0 } }, - { "chansetup_header", "Channel IRC net Password Settings", 0 }, - { "chansetup_line", "$[15]0 %|$[10]1 $[10]2 $3", 4, { 0, 0, 0, 0 } }, - { "chansetup_footer", "", 0 }, - - /* ---- */ - { NULL, "Nick", 0 }, - - { "usermode_change", "Mode change %K[%n%_$0%_%K]%n for user %c$1", 2, { 0, 0 } }, - { "user_mode", "Your user mode is %K[%n%_$0%_%K]", 1, { 0 } }, - { "away", "You have been marked as being away", 0 }, - { "unaway", "You are no longer marked as being away", 0 }, - { "nick_away", "$0 is away: $1", 2, { 0, 0 } }, - { "no_such_nick", "$0: No such nick/channel", 1, { 0 } }, - { "your_nick", "Your nickname is $0", 1, { 0 } }, - { "your_nick_changed", "You're now known as %c$0", 1, { 0 } }, - { "nick_changed", "%_$0%_ is now known as %c$1", 2, { 0, 0 } }, - { "nick_in_use", "Nick %_$0%_ is already in use", 1, { 0 } }, - { "nick_unavailable", "Nick %_$0%_ is temporarily unavailable", 1, { 0 } }, - { "your_nick_owned", "Your nick is owned by %_$3%_ %K[%n$1@$2%K]", 4, { 0, 0, 0, 0 } }, - - /* ---- */ - { NULL, "Who queries", 0 }, - - { "whois", "%_$0%_ %K[%n$1@$2%K]%n%: ircname : $3", 4, { 0, 0, 0, 0 } }, - { "whowas", "%_$0%_ %K[%n$1@$2%K]%n%: ircname : $3", 4, { 0, 0, 0, 0 } }, - { "whois_idle", " idle : $1 days $2 hours $3 mins $4 secs", 5, { 0, 1, 1, 1, 1 } }, - { "whois_idle_signon", " idle : $1 days $2 hours $3 mins $4 secs %K[%nsignon: $5%K]", 6, { 0, 1, 1, 1, 1, 0 } }, - { "whois_server", " server : $1 %K[%n$2%K]", 3, { 0, 0, 0 } }, - { "whois_oper", " : %_IRC operator%_", 1, { 0 } }, - { "whois_registered", " : has registered this nick", 1, { 0 } }, - { "whois_channels", " channels : $1", 2, { 0, 0 } }, - { "whois_away", " away : $1", 2, { 0, 0 } }, - { "end_of_whois", "End of WHOIS", 1, { 0 } }, - { "end_of_whowas", "End of WHOWAS", 1, { 0 } }, - { "whois_not_found", "There is no such nick $0", 1, { 0 } }, - { "who", "$[-10]0 %|%_$[!9]1%_ $[!3]2 $[!2]3 $4@$5 %K(%W$6%K)", 7, { 0, 0, 0, 0, 0, 0, 0 } }, - { "end_of_who", "End of /WHO list", 1, { 0 } }, - - /* ---- */ - { NULL, "Your messages", 0 }, - - { "own_msg", "%K<%n$2%W$0%K>%n %|$1", 3, { 0, 0, 0 } }, - { "own_msg_channel", "%K<%n$3%W$0%K:%c$1%K>%n %|$2", 4, { 0, 0, 0, 0 } }, - { "own_msg_private", "%K[%rmsg%K(%R$0%K)]%n $1", 2, { 0, 0 } }, - { "own_msg_private_query", "%K<%W$2%K>%n %|$1", 3, { 0, 0, 0 } }, - { "own_notice", "%K[%rnotice%K(%R$0%K)]%n $1", 2, { 0, 0 } }, - { "own_me", "%W * $0%n $1", 2, { 0, 0 } }, - { "own_ctcp", "%K[%rctcp%K(%R$0%K)]%n $1 $2", 3, { 0, 0, 0 } }, - { "own_wall", "%K[%WWall%K/%c$0%K]%n $1", 2, { 0, 0 } }, - - /* ---- */ - { NULL, "Received messages", 0 }, - - { "pubmsg_me", "%K<%n$2%Y$0%K>%n %|$1", 3, { 0, 0, 0 } }, - { "pubmsg_me_channel", "%K<%n$3%Y$0%K:%c$1%K>%n %|$2", 4, { 0, 0, 0, 0 } }, - { "pubmsg_hilight", "%K<%n$3$0$1%K>%n %|$2", 4, { 0, 0, 0, 0 } }, - { "pubmsg_hilight_channel", "%K<%n$4$0$1%K:%c$2%K>%n %|$3", 5, { 0, 0, 0, 0, 0 } }, - { "pubmsg", "%K<%n$2$0%K>%n %|$1", 3, { 0, 0, 0 } }, - { "pubmsg_channel", "%K<%n$3$0%K:%c$1%K>%n %|$2", 4, { 0, 0, 0, 0 } }, - { "msg_private", "%K[%R$0%K(%r$1%K)]%n $2", 3, { 0, 0, 0 } }, - { "msg_private_query", "%K<%R$0%K>%n %|$2", 3, { 0, 0, 0 } }, - { "notice_server", "%g!$0%n $1", 2, { 0, 0 } }, - { "notice_public", "%K-%M$0%K:%m$1%K-%n $2", 3, { 0, 0, 0 } }, - { "notice_public_ops", "%K-%M$0%K:%m@$1%K-%n $2", 3, { 0, 0, 0 } }, - { "notice_private", "%K-%M$0%K(%m$1%K)-%n $2", 3, { 0, 0, 0 } }, - { "action_private", "%W (*) $0%n $2", 3, { 0, 0, 0 } }, - { "action_private_query", "%W * $0%n $2", 3, { 0, 0, 0 } }, - { "action_public", "%W * $0%n $1", 2, { 0, 0 } }, - { "action_public_channel", "%W * $0%K:%c$1%n $2", 3, { 0, 0, 0 } }, - - /* ---- */ - { NULL, "CTCPs", 0 }, - - { "ctcp_reply", "CTCP %_$0%_ reply from %_$1%_%K:%n $2", 3, { 0, 0, 0 } }, - { "ctcp_reply_channel", "CTCP %_$0%_ reply from %_$1%_ in channel %_$3%_%K:%n $2", 4, { 0, 0, 0, 0 } }, - { "ctcp_ping_reply", "CTCP %_PING%_ reply from %_$0%_: $1.$2 seconds", 3, { 0, 2, 2 } }, - { "ctcp_requested", "%g>>> %_$0%_ %K[%g$1%K] %grequested %_$2%_ from %_$3", 4, { 0, 0, 0, 0 } }, - - /* ---- */ - { NULL, "Other server events", 0 }, - - { "online", "Users online: %_$0", 1, { 0 } }, - { "pong", "PONG received from $0: $1", 2, { 0, 0 } }, - { "wallops", "%WWALLOP%n $0: $1", 2, { 0, 0 } }, - { "action_wallops", "%WWALLOP * $0%n $1", 2, { 0, 0 } }, - { "error", "%_ERROR%_ $0", 1, { 0 } }, - { "unknown_mode", "Unknown mode character $0", 1, { 0 } }, - { "not_chanop", "You're not channel operator in $0", 1, { 0 } }, - - /* ---- */ +FORMAT_REC fecommon_silc_formats[] = { + { MODULE_NAME, "SILC", 0 }, + + /* Channel related messages */ + { NULL, "Channel", 0 }, + + { "channel_founder_you", "You are channel founder on {channel $0}", 1, { 0 } }, + { "channel_founder", "channel founder on {channel $0} is: {channick_hilight $1}", 2, { 0, 0 } }, + { "channel_topic", "Topic for {channel $0} is: $1", 2, { 0, 0 } }, + { "channel_topic_not_set", "Topic for {channel $0} not set", 1, { 0 } }, + { "cmode", "channel mode/{channel $0} {mode $1} by {nick $2}", 3, { 0, 0, 0 } }, + { "cumode", "channel user mode/{channel $0}/{nick $1} {mode $2} by {nick $3}", 4, { 0, 0, 0, 0 } }, + { "action", "{action $0-}", 2, { 0, 0 } }, + { "notice", "{notice $0-}", 2, { 0, 0 } }, + { "ownaction", "{ownaction $0-}", 2, { 0, 0 } }, + { "ownnotice", "{ownnotice $0-}", 2, { 0, 0 } }, + { "invite_list", "channel {channel $0} invite list: $1", 2, { 0, 0 } }, + { "no_invite_list", "channel {channel $0} invite list not set", 1, { 0 } }, + { "ban_list", "channel {channel $0} ban list: $1", 2, { 0, 0 } }, + { "no_ban_list", "channel {channel $0} ban list not set", 1, { 0 } }, + { "inviting", "Inviting {nick $0} to channel {channel $1}", 2, { 0, 0 } }, + { "kicked_you", "You have been kicked off channel {channel $0} ($1)", 2, { 0, 0 } }, + { "kicked", "{nick $0} has been kicked off channel {channel $1} ($2)", 3, { 0, 0, 0 } }, + { "killed_you", "You have been killed from the SILC Network", 0 }, + { "killed", "{nick $0} has been killed from the SILC Network ($1)", 2, { 0, 0 } }, + + /* WHOIS, WHOWAS and USERS (alias WHO) messages */ + { NULL, "Who Queries", 0 }, + + { "whois", "{nick $0} {nickhost $1@$2}%: nickname : $3 ($4)", 5, { 0, 0, 0, 0, 0 } }, + { "whois_realname", " realname : $0", 1, { 0 } }, + { "whois_channels", " channels : $0", 1, { 0 } }, + { "whois_modes", " modes : $0", 1, { 0 } }, + { "whois_idle", " idle : $0", 1, { 0 } }, + { "whowas", "{nick $0} was {nickhost $1} ($2)", 3, { 0, 0, 0 } }, + { "users_header", "Users on {channelhilight $0}", 1, { 0 } }, + { "users", " %|{nick $[!20]0} $[!5]1 $2@$3 {comment {hilight $4}}", 5, { 0, 0, 0, 0, 0 } }, + + /* Key management and key agreement */ + { NULL, "Key Management And Key Agreement", 0 }, + + { "channel_private_key_add", "Private key set to channel {channel $0}", 1, { 0 } }, + { "channel_private_key_nomode", "Private key mode is not set on channel {channel $0}", 1, { 0 } }, + { "channel_private_key_error", "Could not add private key to channel {channel $0}", 1, { 0 } }, + { "channel_private_key_list", "Channel {channel $0} private keys%: Cipher Hmac Key", 1, { 0 } }, + { "private_key_list", "Private message keys%: Client Cipher Key", 0 }, + { "private_key_list_nick", "Private message keys with {nick $0}%: Client Cipher Key", 1, { 0 } }, + { "key_agreement", "Requesting key agreement with {nick $0}", 1, { 0 } }, + { "key_agreement_request", "{nick $0} wants to perform key agreement", 1, { 0 } }, + { "key_agreement_request_host", "{nick $0} wants to perform key agreement on {nickhost $1} port {hilight $2}", 3, { 0, 0, 0 } }, + { "key_agreement_negotiate", "Starting key agreement with {nick $0}", 1, { 0 } }, + { "key_agreement_privmsg", "The private messages with the {nick $0} are now protected with the private key", 1, { 0 } }, + { "key_agreement_ok", "Key agreement completed successfully with {nick $0}", 1, { 0 } }, + { "key_agreement_error", "Error occurred during key agreement with {nick $0}", 1, { 0 } }, + { "key_agreement_failure", "Key agreement failed with {nick $0}", 1, { 0 } }, + { "key_agreement_timeout", "Timeout during key agreement. The key agreement was not performed with {nick $0}", 1, { 0 } }, + { "pubkey_received", "Received {hilight $0} public key", 1, { 0 } }, + { "pubkey_fingerprint", "Fingerprint and babbleprint for the {hilight $0} key are %: $1", 2, { 0, 0 } }, + { "pubkey_babbleprint", " $0", 1, { 0 } }, + { "pubkey_unsupported", "We don't support {hilight $0} public key type {hilight $1}", 2, { 0, 0 } }, + { "pubkey_discard", "Will not accept the {hilight $0} key", 1, { 0 } }, + { "pubkey_accept", "Would you like to accept the key (y/n)? ", 0 }, + { "pubkey_accept_anyway", "Would you like to accept the key anyway (y/n)? ", 0 }, + { "pubkey_could_not_load", "Could not load your local copy of the {hilight $0} key", 1, { 0 } }, + { "pubkey_malformed", "Your local copy of the {hilight $0} key is malformed", 1, { 0 } }, + { "pubkey_no_match", "{hilight $0} key does not match with your local copy", 1, { 0 } }, + { "pubkey_maybe_expired", "It is possible that the key has expired or changed", 0 }, + { "pubkey_mitm_attach", "It is also possible that someone is performing man-in-the-middle attack", 0 }, + { "getkey_notkey", "Server did not return any public key", 0 }, + { "getkey_verified", "Verified successfully $0 {hilight $1}'s cached public key", 2, { 0, 0 } }, + { "getkey_discard", "Could not verify $0 {hilight $1}'s public key", 2, { 0, 0 } }, + + /* Misc messages */ { NULL, "Misc", 0 }, - { "ignored", "Ignoring %_$1%_ from %_$0%_", 2, { 0, 0 } }, - { "unignored", "Unignored %_$0%_", 1, { 0 } }, - { "ignore_not_found", "%_$0%_ is not being ignored", 1, { 0 } }, - { "ignore_no_ignores", "There are no ignores", 0 }, - { "ignore_header", "Ignorance List:", 0 }, - { "ignore_line", "$[-4]0 $1: $2 $3 $4", 4, { 1, 0, 0, 0 } }, - { "ignore_footer", "", 0 }, - { "talking_in", "You are now talking in %_$0%_", 1, { 0 } }, - { "query_start", "Starting query with %_$0%_", 1, { 0 } }, - { "no_query", "No query with %_$0%_", 1, { 0 } }, - { "no_msgs_got", "You have not received a message from anyone yet", 0 }, - { "no_msgs_sent", "You have not sent a message to anyone yet", 0 }, + { "server_oper", "You are now {hilight server operator}", 0 }, + { "router_oper", "You are now {hilight SILC operator}", 0 }, + { "list_header", " Channel Users Topic", 0 }, + { "list", " %|{channelhilight $[36]0} {hilight $[7]1} $2", 3, { 0, 0, 0 } }, + { "bad_nick", "Bad nickname {hilight $0}", 1, { 0 } }, + { "unknown_notify", "Unknown notify type {hilight $0}", 1, { 0 } }, + { "ke_bad_version", "You are running incompatible client version (it may be too old or too new) ", 0 }, + { "ke_unsupported_public_key", "Server does not support your public key type", 0 }, + { "ke_unknown_group", "Server does not support one of your proposed KE group", 0 }, + { "ke_unknown_cipher", "Server does not support one of your proposed cipher", 0 }, + { "ke_unknown_pkcs", "Server does not support one of your proposed PKCS", 0 }, + { "ke_unknown_hash_function", "Server does not support one of your proposed hash function", 0 }, + { "ke_unknown_hmac", "Server does not support one of your proposed HMAC", 0 }, + { "ke_incorrect_signature", "Incorrect signature", 0 }, + { "ke_invalid_cookie", "Invalid cookie", 0 }, + { "auth_failed", "Authentication failed", 0 }, + { "set_away", "You have meen marked as being away (away message: {hilight $0})", 1, { 0 } }, + { "unset_away", "You are no longer marked as being away", 0 }, + { "auth_meth_unresolved", "Could not resolve authentication method to use, assume no authentication", 0 }, + + /* File transfer messages */ + { NULL, "FileTransfer", 0 }, + + { "file_send", "File transfer request sent to {nick $0} for $1", 2, { 0, 0 } }, + { "file_transmit", "Transmitting file {hilight $0} [$1kB] to {nick $2}", 3, { 0, 0, 0 } }, + { "file_transmitted", "Transmitted file {hilight $0} [$1kB] to {nick $2} [{hilight $3kB/s}]", 4, { 0, 0, 0, 3 } }, + { "file_receive", "Receiving file {hilight $0} [$1kB] from {nick $2}", 3, { 0, 0 } }, + { "file_received", "Received file {hilight $0} [$1kB] from {nick $2} [{hilight $3kB/s}]", 4, { 0, 0, 0, 3 } }, + { "file_request", "File transfer request from {nick $0}", 1, { 0 } }, + { "file_request_host", "File transfer request from {nick $0} [$1 port $2]", 3, { 0, 0, 0 } }, + { "file_key_exchange", "Negotiating keys for file transfer with {nick $0}", 1, { 0 } }, + { "file_na", "No file transfers available", 0 }, + { "file_client_na", "No file transfer offered by {nick $0}", 1, { 0 } }, + { "file_show_header", "File transfers", 0 }, + { "file_show_line", " $0 $1: $2kB of $3kB ($4%%) - $5kB/s - $6", 7, { 0, 0, 1, 1, 1, 3, 0 } }, + { "file_already_started", "File transfer already started with {nick $0}", 1, { 0 } }, + { "file_error", "Error during file transfer with {nick $0}", 1, { 0 } }, + { "file_error_no_such_file", "Error during file transfer with {nick $0}: $1: No such file", 2, { 0, 0 } }, + { "file_error_permission_denied", "Error during file transfer with {nick $0}: Permission denied", 1, { 0 } }, + { "file_close", "File transfer closed with {nick $0} - $1", 2, { 0, 0 } }, { NULL, NULL, 0 } }; diff --git a/apps/irssi/src/fe-common/silc/module-formats.h b/apps/irssi/src/fe-common/silc/module-formats.h index aa4c87c3..c1d94a34 100644 --- a/apps/irssi/src/fe-common/silc/module-formats.h +++ b/apps/irssi/src/fe-common/silc/module-formats.h @@ -1,181 +1,135 @@ -#include "printtext.h" +/* + + modules-formats.h + + Author: Pekka Riikonen + + Copyright (C) 2001 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. + +*/ + +#include "fe-common/core/formats.h" enum { - IRCTXT_MODULE_NAME, - - IRCTXT_FILL_1, - - IRCTXT_LAG_DISCONNECTED, - IRCTXT_DISCONNECTED, - IRCTXT_SERVER_LIST, - IRCTXT_SERVER_LOOKUP_LIST, - IRCTXT_SERVER_RECONNECT_LIST, - IRCTXT_RECONNECT_REMOVED, - IRCTXT_RECONNECT_NOT_FOUND, - IRCTXT_QUERY_SERVER_CHANGED, - IRCTXT_SETUPSERVER_ADDED, - IRCTXT_SETUPSERVER_REMOVED, - IRCTXT_SETUPSERVER_NOT_FOUND, - IRCTXT_SETUPSERVER_HEADER, - IRCTXT_SETUPSERVER_LINE, - IRCTXT_SETUPSERVER_FOOTER, - IRCTXT_NETSPLIT, - IRCTXT_NETSPLIT_MORE, - IRCTXT_NETSPLIT_JOIN, - IRCTXT_NETSPLIT_JOIN_MORE, - IRCTXT_NO_NETSPLITS, - IRCTXT_NETSPLITS_HEADER, - IRCTXT_NETSPLITS_LINE, - IRCTXT_NETSPLITS_FOOTER, - IRCTXT_IRCNET_ADDED, - IRCTXT_IRCNET_REMOVED, - IRCTXT_IRCNET_NOT_FOUND, - IRCTXT_IRCNET_HEADER, - IRCTXT_IRCNET_LINE, - IRCTXT_IRCNET_FOOTER, - - IRCTXT_FILL_2, - - IRCTXT_JOIN, - IRCTXT_PART, - IRCTXT_JOINERROR_TOOMANY, - IRCTXT_JOINERROR_FULL, - IRCTXT_JOINERROR_INVITE, - IRCTXT_JOINERROR_BANNED, - IRCTXT_JOINERROR_BAD_KEY, - IRCTXT_JOINERROR_BAD_MASK, - IRCTXT_JOINERROR_UNAVAIL, - IRCTXT_KICK, - IRCTXT_QUIT, - IRCTXT_QUIT_ONCE, - IRCTXT_INVITE, - IRCTXT_INVITING, - IRCTXT_NOT_INVITED, - IRCTXT_NAMES, - IRCTXT_NAMES_NICK, - IRCTXT_ENDOFNAMES, - IRCTXT_CHANNEL_CREATED, - IRCTXT_TOPIC, - IRCTXT_NO_TOPIC, - IRCTXT_NEW_TOPIC, - IRCTXT_TOPIC_UNSET, - IRCTXT_TOPIC_INFO, - IRCTXT_CHANMODE_CHANGE, - IRCTXT_SERVER_CHANMODE_CHANGE, - IRCTXT_CHANNEL_MODE, - IRCTXT_BANTYPE, - IRCTXT_NO_BANS, - IRCTXT_BANLIST, - IRCTXT_BANLIST_LONG, - IRCTXT_EBANLIST, - IRCTXT_EBANLIST_LONG, - IRCTXT_INVITELIST, - IRCTXT_NO_SUCH_CHANNEL, - IRCTXT_CHANNEL_SYNCED, - IRCTXT_NOT_IN_CHANNELS, - IRCTXT_CURRENT_CHANNEL, - IRCTXT_CHANLIST_HEADER, - IRCTXT_CHANLIST_LINE, - IRCTXT_CHANSETUP_NOT_FOUND, - IRCTXT_CHANSETUP_ADDED, - IRCTXT_CHANSETUP_REMOVED, - IRCTXT_CHANSETUP_HEADER, - IRCTXT_CHANSETUP_LINE, - IRCTXT_CHANSETUP_FOOTER, - - IRCTXT_FILL_4, - - IRCTXT_USERMODE_CHANGE, - IRCTXT_USER_MODE, - IRCTXT_AWAY, - IRCTXT_UNAWAY, - IRCTXT_NICK_AWAY, - IRCTXT_NO_SUCH_NICK, - IRCTXT_YOUR_NICK, - IRCTXT_YOUR_NICK_CHANGED, - IRCTXT_NICK_CHANGED, - IRCTXT_NICK_IN_USE, - IRCTXT_NICK_UNAVAILABLE, - IRCTXT_YOUR_NICK_OWNED, - - IRCTXT_FILL_5, - - IRCTXT_WHOIS, - IRCTXT_WHOWAS, - IRCTXT_WHOIS_IDLE, - IRCTXT_WHOIS_IDLE_SIGNON, - IRCTXT_WHOIS_SERVER, - IRCTXT_WHOIS_OPER, - IRCTXT_WHOIS_REGISTERED, - IRCTXT_WHOIS_CHANNELS, - IRCTXT_WHOIS_AWAY, - IRCTXT_END_OF_WHOIS, - IRCTXT_END_OF_WHOWAS, - IRCTXT_WHOIS_NOT_FOUND, - IRCTXT_WHO, - IRCTXT_END_OF_WHO, - - IRCTXT_FILL_6, - - IRCTXT_OWN_MSG, - IRCTXT_OWN_MSG_CHANNEL, - IRCTXT_OWN_MSG_PRIVATE, - IRCTXT_OWN_MSG_PRIVATE_QUERY, - IRCTXT_OWN_NOTICE, - IRCTXT_OWN_ME, - IRCTXT_OWN_CTCP, - IRCTXT_OWN_WALL, - - IRCTXT_FILL_7, - - IRCTXT_PUBMSG_ME, - IRCTXT_PUBMSG_ME_CHANNEL, - IRCTXT_PUBMSG_HILIGHT, - IRCTXT_PUBMSG_HILIGHT_CHANNEL, - IRCTXT_PUBMSG, - IRCTXT_PUBMSG_CHANNEL, - IRCTXT_MSG_PRIVATE, - IRCTXT_MSG_PRIVATE_QUERY, - IRCTXT_NOTICE_SERVER, - IRCTXT_NOTICE_PUBLIC, - IRCTXT_NOTICE_PUBLIC_OPS, - IRCTXT_NOTICE_PRIVATE, - IRCTXT_ACTION_PRIVATE, - IRCTXT_ACTION_PRIVATE_QUERY, - IRCTXT_ACTION_PUBLIC, - IRCTXT_ACTION_PUBLIC_CHANNEL, - - IRCTXT_FILL_8, - - IRCTXT_CTCP_REPLY, - IRCTXT_CTCP_REPLY_CHANNEL, - IRCTXT_CTCP_PING_REPLY, - IRCTXT_CTCP_REQUESTED, - - IRCTXT_FILL_10, - - IRCTXT_ONLINE, - IRCTXT_PONG, - IRCTXT_WALLOPS, - IRCTXT_ACTION_WALLOPS, - IRCTXT_ERROR, - IRCTXT_UNKNOWN_MODE, - IRCTXT_NOT_CHANOP, - - IRCTXT_FILL_11, - - IRCTXT_IGNORED, - IRCTXT_UNIGNORED, - IRCTXT_IGNORE_NOT_FOUND, - IRCTXT_IGNORE_NO_IGNORES, - IRCTXT_IGNORE_HEADER, - IRCTXT_IGNORE_LINE, - IRCTXT_IGNORE_FOOTER, - IRCTXT_TALKING_IN, - IRCTXT_QUERY_STARTED, - IRCTXT_NO_QUERY, - IRCTXT_NO_MSGS_GOT, - IRCTXT_NO_MSGS_SENT + SILCTXT_MODULE_NAME, + + SILCTXT_FILL_1, + + SILCTXT_CHANNEL_FOUNDER_YOU, + SILCTXT_CHANNEL_FOUNDER, + SILCTXT_CHANNEL_TOPIC, + SILCTXT_CHANNEL_TOPIC_NOT_SET, + SILCTXT_CHANNEL_CMODE, + SILCTXT_CHANNEL_CUMODE, + SILCTXT_CHANNEL_ACTION, + SILCTXT_CHANNEL_NOTICE, + SILCTXT_CHANNEL_OWNACTION, + SILCTXT_CHANNEL_OWNNOTICE, + SILCTXT_CHANNEL_INVITE_LIST, + SILCTXT_CHANNEL_NO_INVITE_LIST, + SILCTXT_CHANNEL_BAN_LIST, + SILCTXT_CHANNEL_NO_BAN_LIST, + SILCTXT_CHANNEL_INVITING, + SILCTXT_CHANNEL_KICKED_YOU, + SILCTXT_CHANNEL_KICKED, + SILCTXT_CHANNEL_KILLED_YOU, + SILCTXT_CHANNEL_KILLED, + + SILCTXT_FILL_2, + + SILCTXT_WHOIS_USERINFO, + SILCTXT_WHOIS_REALNAME, + SILCTXT_WHOIS_CHANNELS, + SILCTXT_WHOIS_MODES, + SILCTXT_WHOIS_IDLE, + SILCTXT_WHOWAS_USERINFO, + SILCTXT_USERS_HEADER, + SILCTXT_USERS, + + SILCTXT_FILL_3, + + SILCTXT_CH_PRIVATE_KEY_ADD, + SILCTXT_CH_PRIVATE_KEY_NOMODE, + SILCTXT_CH_PRIVATE_KEY_ERROR, + SILCTXT_CH_PRIVATE_KEY_LIST, + SILCTXT_PRIVATE_KEY_LIST, + SILCTXT_PRIVATE_KEY_LIST_NICK, + SILCTXT_KEY_AGREEMENT, + SILCTXT_KEY_AGREEMENT_REQUEST, + SILCTXT_KEY_AGREEMENT_REQUEST_HOST, + SILCTXT_KEY_AGREEMENT_NEGOTIATE, + SILCTXT_KEY_AGREEMENT_PRIVMSG, + SILCTXT_KEY_AGREEMENT_OK, + SILCTXT_KEY_AGREEMENT_ERROR, + SILCTXT_KEY_AGREEMENT_FAILURE, + SILCTXT_KEY_AGREEMENT_TIMEOUT, + SILCTXT_PUBKEY_RECEIVED, + SILCTXT_PUBKEY_FINGERPRINT, + SILCTXT_PUBKEY_BABBLEPRINT, + SILCTXT_PUBKEY_UNSUPPORTED, + SILCTXT_PUBKEY_DISCARD, + SILCTXT_PUBKEY_ACCEPT, + SILCTXT_PUBKEY_ACCEPT_ANYWAY, + SILCTXT_PUBKEY_COULD_NOT_LOAD, + SILCTXT_PUBKEY_MALFORMED, + SILCTXT_PUBKEY_NO_MATCH, + SILCTXT_PUBKEY_MAYBE_EXPIRED, + SILCTXT_PUBKEY_MITM_ATTACK, + SILCTXT_GETKEY_NOKEY, + SILCTXT_GETKEY_VERIFIED, + SILCTXT_GETKEY_DISCARD, + + SILCTXT_FILL_4, + + SILCTXT_SERVER_OPER, + SILCTXT_ROUTER_OPER, + SILCTXT_LIST_HEADER, + SILCTXT_LIST, + SILCTXT_BAD_NICK, + SILCTXT_UNKNOWN_NOTIFY, + SILCTXT_KE_BAD_VERSION, + SILCTXT_KE_UNSUPPORTED_PUBLIC_KEY, + SILCTXT_KE_UNKNOWN_GROUP, + SILCTXT_KE_UNKNOWN_CIPHER, + SILCTXT_KE_UNKNOWN_PKCS, + SILCTXT_KE_UNKNOWN_HASH_FUNCTION, + SILCTXT_KE_UNKNOWN_HMAC, + SILCTXT_KE_INCORRECT_SIGNATURE, + SILCTXT_KE_INVALID_COOKIE, + SILCTXT_AUTH_FAILED, + SILCTXT_SET_AWAY, + SILCTXT_UNSET_AWAY, + SILCTXT_AUTH_METH_UNRESOLVED, + + SILCTXT_FILL_5, + + SILCTXT_FILE_SEND, + SILCTXT_FILE_TRANSMIT, + SILCTXT_FILE_TRANSMITTED, + SILCTXT_FILE_RECEIVE, + SILCTXT_FILE_RECEIVED, + SILCTXT_FILE_REQUEST, + SILCTXT_FILE_REQUEST_HOST, + SILCTXT_FILE_KEY_EXCHANGE, + SILCTXT_FILE_NA, + SILCTXT_FILE_CLIENT_NA, + SILCTXT_FILE_SHOW_HEADER, + SILCTXT_FILE_SHOW_LINE, + SILCTXT_FILE_ALREADY_STARTED, + SILCTXT_FILE_ERROR, + SILCTXT_FILE_ERROR_NO_SUCH_FILE, + SILCTXT_FILE_ERROR_PERMISSION_DENIED, + SILCTXT_FILE_CLOSED, }; -extern FORMAT_REC fecommon_irc_formats[]; +extern FORMAT_REC fecommon_silc_formats[]; diff --git a/apps/irssi/src/fe-common/silc/silc-modules.c b/apps/irssi/src/fe-common/silc/silc-modules.c deleted file mode 100644 index 16081e1a..00000000 --- a/apps/irssi/src/fe-common/silc/silc-modules.c +++ /dev/null @@ -1,3 +0,0 @@ -/* this file is automatically generated by configure - don't change */ -void fe_silc_modules_init(void) { } -void fe_silc_modules_deinit(void) { } diff --git a/apps/irssi/src/fe-text/Makefile.am b/apps/irssi/src/fe-text/Makefile.am index ef0f5616..326ec232 100644 --- a/apps/irssi/src/fe-text/Makefile.am +++ b/apps/irssi/src/fe-text/Makefile.am @@ -1,6 +1,8 @@ bin_PROGRAMS = silc -INCLUDES = \ +include $(top_srcdir)/Makefile.defines.in + +ADD_INCLUDES = \ $(GLIB_CFLAGS) \ -I$(top_srcdir)/src \ -I$(top_srcdir)/src/core/ \ @@ -12,13 +14,15 @@ INCLUDES = \ silc_DEPENDENCIES = @COMMON_LIBS@ +LIBS = $(SILC_COMMON_LIBS) silc_LDADD = \ @COMMON_LIBS@ \ @PERL_LINK_LIBS@ \ @PERL_FE_LINK_LIBS@ \ $(PROG_LIBS) \ $(CURSES_LIBS) \ - -L../../../lib -lsilcclient -lsilc + -L../../../lib -lsilcclient + silc_SOURCES = \ gui-entry.c \ diff --git a/apps/irssi/src/fe-text/gui-windows.c b/apps/irssi/src/fe-text/gui-windows.c index 9a199cdd..5d7ad0d7 100644 --- a/apps/irssi/src/fe-text/gui-windows.c +++ b/apps/irssi/src/fe-text/gui-windows.c @@ -217,9 +217,10 @@ static MAIN_WINDOW_REC *mainwindow_find_unsticky(void) return active_mainwin; } -static void signal_window_changed(WINDOW_REC *window, WINDOW_REC *old_window) +static void signal_window_changed(WINDOW_REC *window) { MAIN_WINDOW_REC *parent; + WINDOW_REC *old_window; g_return_if_fail(window != NULL); @@ -245,10 +246,11 @@ static void signal_window_changed(WINDOW_REC *window, WINDOW_REC *old_window) } gui_window_reparent(window, active_mainwin); } - active_mainwin->active = window; - if (old_window != NULL && !is_window_visible(old_window)) + old_window = active_mainwin->active; + if (old_window != NULL) textbuffer_view_set_window(WINDOW_GUI(old_window)->view, NULL); + active_mainwin->active = window; textbuffer_view_set_window(WINDOW_GUI(window)->view, parent->curses_win); diff --git a/apps/irssi/src/fe-text/silc.c b/apps/irssi/src/fe-text/silc.c index 9112b568..a4203e47 100644 --- a/apps/irssi/src/fe-text/silc.c +++ b/apps/irssi/src/fe-text/silc.c @@ -148,10 +148,12 @@ static void textui_finish_init(void) #endif signal_emit("irssi init finished", 0); +#if 0 if (display_firsttimer) { printtext_window(active_win, MSGLEVEL_CLIENTNOTICE, "%s", firsttimer_text); } +#endif screen_refresh_thaw(); } @@ -204,7 +206,7 @@ static void check_oldcrap(void) int found; /* check that default.theme is up-to-date */ - path = g_strdup_printf("%s/.irssi/default.theme", g_get_home_dir()); + path = g_strdup_printf("%s/.silc/default.theme", g_get_home_dir()); f = fopen(path, "r+"); if (f == NULL) { g_free(path); @@ -220,7 +222,7 @@ static void check_oldcrap(void) return; } - printf("\nYou seem to have old default.theme in ~/.irssi/ directory.\n"); + printf("\nYou seem to have old default.theme in ~/.silc/ directory.\n"); printf("Themeing system has changed a bit since last irssi release,\n"); printf("you should either delete your old default.theme or manually\n"); printf("merge it with the new default.theme.\n\n"); @@ -238,7 +240,7 @@ static void check_files(void) struct stat statbuf; char *path; - path = g_strdup_printf("%s/.irssi", g_get_home_dir()); + path = g_strdup_printf("%s/.silc", g_get_home_dir()); if (stat(path, &statbuf) != 0) { /* ~/.irssi doesn't exist, first time running irssi */ display_firsttimer = TRUE; @@ -280,6 +282,7 @@ int main(int argc, char **argv) textui_init(); args_execute(argc, argv); + silc_init_finish(); if (!init_screen()) g_error("Can't initialize screen handling, quitting.\n"); diff --git a/apps/irssi/src/fe-text/textbuffer-view.c b/apps/irssi/src/fe-text/textbuffer-view.c index f3a74080..449f20a9 100644 --- a/apps/irssi/src/fe-text/textbuffer-view.c +++ b/apps/irssi/src/fe-text/textbuffer-view.c @@ -588,9 +588,6 @@ void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height) g_return_if_fail(view != NULL); g_return_if_fail(width > 0); - if (view->buffer->lines == NULL) - return; - if (view->width != width) { /* line cache needs to be recreated */ textbuffer_cache_unref(view->cache); @@ -600,6 +597,12 @@ void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height) view->width = width; view->height = height; + + if (view->buffer->lines == NULL) { + view->empty_linecount = height; + return; + } + textbuffer_view_init_bottom(view); /* check that we didn't scroll lower than bottom startline.. */ @@ -841,7 +844,7 @@ static void bookmark_check_remove(char *key, LINE_REC *line, static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { BOOKMARK_FIND_REC rec; - LINE_REC *newline; + LINE_REC *new_line; GSList *tmp; rec.remove_line = line; @@ -852,14 +855,14 @@ static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) if (rec.remove_list != NULL) { GList *pos = g_list_find(view->buffer->lines, line); - newline = pos == NULL || pos->prev == NULL ? NULL : + new_line = pos == NULL || pos->prev == NULL ? NULL : (pos->next == NULL ? pos->prev->data : pos->next->data); for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) { g_hash_table_remove(view->bookmarks, tmp->data); - if (newline != NULL) { + if (new_line != NULL) { g_hash_table_insert(view->bookmarks, - tmp->data, newline); + tmp->data, new_line); } } g_slist_free(rec.remove_list); diff --git a/apps/irssi/src/silc/core/Makefile.am b/apps/irssi/src/silc/core/Makefile.am new file mode 100644 index 00000000..769899ee --- /dev/null +++ b/apps/irssi/src/silc/core/Makefile.am @@ -0,0 +1,37 @@ +include $(top_srcdir)/Makefile.defines.in + +IRSSI_INCLUDE=../../.. + +ADD_INCLUDES = \ + $(GLIB_CFLAGS) -I$(IRSSI_INCLUDE) -I$(IRSSI_INCLUDE)/src \ + -DSYSCONFDIR=\""$(silc_etcdir)"\" \ + -I$(IRSSI_INCLUDE) \ + -I$(IRSSI_INCLUDE)/src \ + -I$(IRSSI_INCLUDE)/src/core \ + -I$(IRSSI_INCLUDE)/src/fe-common/core \ + -I$(IRSSI_INCLUDE)/src/fe-common/silc + +noinst_LIBRARIES=libsilc_core.a + +libsilc_core_a_SOURCES = \ + client_ops.c \ + clientutil.c \ + clientconfig.c \ + silc-channels.c \ + silc-core.c \ + silc-nicklist.c \ + silc-queries.c \ + silc-servers.c \ + silc-servers-reconnect.c + +noinst_HEADERS = \ + module.h \ + client_ops.h \ + clientutil.h \ + clientconfig.h \ + silc-channels.h \ + silc-core.h \ + silc-nicklist.h \ + silc-queries.h \ + silc-servers.h + diff --git a/apps/irssi/src/silc/core/client_ops.c b/apps/irssi/src/silc/core/client_ops.c new file mode 100644 index 00000000..a3af5d41 --- /dev/null +++ b/apps/irssi/src/silc/core/client_ops.c @@ -0,0 +1,1272 @@ +/* + + client_ops.c + + Author: Pekka Riikonen + + Copyright (C) 2001 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. + +*/ + +#include "module.h" +#include "chat-protocols.h" +#include "args.h" + +#include "chatnets.h" +#include "servers-setup.h" +#include "channels-setup.h" +#include "silc-servers.h" +#include "silc-channels.h" +#include "silc-queries.h" +#include "silc-nicklist.h" + +#include "signals.h" +#include "levels.h" +#include "settings.h" +#include "fe-common/core/printtext.h" +#include "fe-common/core/fe-channels.h" +#include "fe-common/core/keyboard.h" +#include "fe-common/silc/module-formats.h" + +static void +silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context); + +void silc_say(SilcClient client, SilcClientConnection conn, + SilcClientMessageType type, char *msg, ...) +{ + SILC_SERVER_REC *server; + va_list va; + char *str; + + server = conn == NULL ? NULL : conn->context; + + va_start(va, msg); + str = g_strdup_vprintf(msg, va); + printtext(server, NULL, MSGLEVEL_CRAP, "%s", str); + g_free(str); + va_end(va); +} + +void silc_say_error(char *msg, ...) +{ + va_list va; + char *str; + + va_start(va, msg); + str = g_strdup_vprintf(msg, va); + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "%s", str); + + g_free(str); + va_end(va); +} + +/* Message for a channel. The `sender' is the nickname of the sender + received in the packet. The `channel_name' is the name of the channel. */ + +void silc_channel_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcChannelEntry channel, + SilcMessageFlags flags, char *msg) +{ + SILC_SERVER_REC *server; + SILC_NICK_REC *nick; + SILC_CHANNEL_REC *chanrec; + + SILC_LOG_DEBUG(("Start")); + + server = conn == NULL ? NULL : conn->context; + chanrec = silc_channel_find_entry(server, channel); + if (!chanrec) + return; + + nick = silc_nicklist_find(chanrec, sender); + if (!nick) { + /* We didn't find client but it clearly exists, add it. */ + SilcChannelUser chu; + + silc_list_start(channel->clients); + while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) { + if (chu->client == sender) { + nick = silc_nicklist_insert(chanrec, chu, FALSE); + break; + } + } + } + + if (flags & SILC_MESSAGE_FLAG_ACTION) + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_ACTIONS, SILCTXT_CHANNEL_ACTION, + nick == NULL ? "[]" : nick->nick, msg); + else if (flags & SILC_MESSAGE_FLAG_NOTICE) + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_NOTICES, SILCTXT_CHANNEL_NOTICE, + nick == NULL ? "[]" : nick->nick, msg); + else + signal_emit("message public", 6, server, msg, + nick == NULL ? "[]" : nick->nick, + nick == NULL ? "" : nick->host == NULL ? "" : nick->host, + chanrec->name, nick); +} + +/* Private message to the client. The `sender' is the nickname of the + sender received in the packet. */ + +void silc_private_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcMessageFlags flags, + char *msg) +{ + SILC_SERVER_REC *server; + char userhost[256]; + + SILC_LOG_DEBUG(("Start")); + + server = conn == NULL ? NULL : conn->context; + memset(userhost, 0, sizeof(userhost)); + if (sender->username) + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + sender->username, sender->hostname); + signal_emit("message private", 4, server, msg, + sender->nickname ? sender->nickname : "[]", + sender->username ? userhost : NULL); +} + +/* Notify message to the client. The notify arguments are sent in the + same order as servers sends them. The arguments are same as received + from the server except for ID's. If ID is received application receives + the corresponding entry to the ID. For example, if Client ID is received + application receives SilcClientEntry. Also, if the notify type is + for channel the channel entry is sent to application (even if server + does not send it). */ + +typedef struct { + int type; + const char *name; +} NOTIFY_REC; + +#define MAX_NOTIFY (sizeof(notifies)/sizeof(notifies[0])) +static NOTIFY_REC notifies[] = { + { SILC_NOTIFY_TYPE_NONE, NULL }, + { SILC_NOTIFY_TYPE_INVITE, "invite" }, + { SILC_NOTIFY_TYPE_JOIN, "join" }, + { SILC_NOTIFY_TYPE_LEAVE, "leave" }, + { SILC_NOTIFY_TYPE_SIGNOFF, "signoff" }, + { SILC_NOTIFY_TYPE_TOPIC_SET, "topic" }, + { SILC_NOTIFY_TYPE_NICK_CHANGE, "nick" }, + { SILC_NOTIFY_TYPE_CMODE_CHANGE, "cmode" }, + { SILC_NOTIFY_TYPE_CUMODE_CHANGE, "cumode" }, + { SILC_NOTIFY_TYPE_MOTD, "motd" }, + { SILC_NOTIFY_TYPE_CHANNEL_CHANGE, "channel_change" }, + { SILC_NOTIFY_TYPE_SERVER_SIGNOFF, "server_signoff" }, + { SILC_NOTIFY_TYPE_KICKED, "kick" }, + { SILC_NOTIFY_TYPE_KILLED, "kill" }, + { SILC_NOTIFY_TYPE_UMODE_CHANGE, "umode" }, + { SILC_NOTIFY_TYPE_BAN, "ban" }, +}; + +void silc_notify(SilcClient client, SilcClientConnection conn, + SilcNotifyType type, ...) +{ + SILC_SERVER_REC *server; + va_list va; + + SILC_LOG_DEBUG(("Start")); + + server = conn == NULL ? NULL : conn->context; + va_start(va, type); + + if (type == SILC_NOTIFY_TYPE_NONE) { + /* Some generic notice from server */ + printtext(server, NULL, MSGLEVEL_CRAP, "%s", (char *)va_arg(va, char *)); + } else if (type < MAX_NOTIFY) { + /* Send signal about the notify event */ + char signal[50]; + g_snprintf(signal, sizeof(signal), "silc event %s", notifies[type].name); + signal_emit(signal, 2, server, va); + } else { + /* Unknown notify */ + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_UNKNOWN_NOTIFY, type); + } + + va_end(va); +} + +/* Called to indicate that connection was either successfully established + or connecting failed. This is also the first time application receives + the SilcClientConnection objecet which it should save somewhere. */ + +void silc_connect(SilcClient client, SilcClientConnection conn, int success) +{ + SILC_SERVER_REC *server = conn->context; + + if (!server && !success) { + silc_client_close_connection(client, NULL, conn); + return; + } + + if (success) { + server->connected = TRUE; + signal_emit("event connected", 1, server); + } else { + server->connection_lost = TRUE; + server->conn->context = NULL; + server_disconnect(SERVER(server)); + } +} + +/* Called to indicate that connection was disconnected to the server. */ + +void silc_disconnect(SilcClient client, SilcClientConnection conn) +{ + SILC_SERVER_REC *server = conn->context; + + SILC_LOG_DEBUG(("Start")); + + if (server->conn) { + nicklist_rename_unique(SERVER(server), + server->conn->local_entry, server->nick, + server->conn->local_entry, + silc_client->username); + silc_change_nick(server, silc_client->username); + } + + server->conn->context = NULL; + server->conn = NULL; + server->connection_lost = TRUE; + server_disconnect(SERVER(server)); +} + +/* Command handler. This function is called always in the command function. + If error occurs it will be called as well. `conn' is the associated + client connection. `cmd_context' is the command context that was + originally sent to the command. `success' is FALSE if error occured + during command. `command' is the command being processed. It must be + noted that this is not reply from server. This is merely called just + after application has called the command. Just to tell application + that the command really was processed. */ + +void silc_command(SilcClient client, SilcClientConnection conn, + SilcClientCommandContext cmd_context, int success, + SilcCommand command) +{ + SILC_SERVER_REC *server = conn->context; + + SILC_LOG_DEBUG(("Start")); + + if (!success) + return; + + switch(command) { + case SILC_COMMAND_INVITE: + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_INVITING, + cmd_context->argv[2], + (cmd_context->argv[1][0] == '*' ? + (char *)conn->current_channel->channel_name : + (char *)cmd_context->argv[1])); + break; + default: + break; + } +} + +/* Client info resolving callback when JOIN command reply is received. + This will cache all users on the channel. */ + +static void silc_client_join_get_users(SilcClient client, + SilcClientConnection conn, + SilcClientEntry *clients, + uint32 clients_count, + void *context) +{ + SilcChannelEntry channel = (SilcChannelEntry)context; + SilcChannelUser chu; + SILC_SERVER_REC *server = conn->context; + SILC_CHANNEL_REC *chanrec; + SilcClientEntry founder = NULL; + NICK_REC *ownnick; + + if (!clients) + return; + + chanrec = silc_channel_find(server, channel->channel_name); + if (chanrec == NULL) + return; + + silc_list_start(channel->clients); + while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) { + if (!chu->client->nickname) + continue; + if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) + founder = chu->client; + silc_nicklist_insert(chanrec, chu, FALSE); + } + + ownnick = NICK(silc_nicklist_find(chanrec, conn->local_entry)); + nicklist_set_own(CHANNEL(chanrec), ownnick); + signal_emit("channel joined", 1, chanrec); + + if (chanrec->topic) + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_TOPIC, + channel->channel_name, chanrec->topic); + + fe_channels_nicklist(CHANNEL(chanrec), CHANNEL_NICKLIST_FLAG_ALL); + + if (founder) { + if (founder == conn->local_entry) + printformat_module("fe-common/silc", + server, channel->channel_name, MSGLEVEL_CRAP, + SILCTXT_CHANNEL_FOUNDER_YOU, + channel->channel_name); + else + printformat_module("fe-common/silc", + server, channel->channel_name, MSGLEVEL_CRAP, + SILCTXT_CHANNEL_FOUNDER, + channel->channel_name, founder->nickname); + } +} + +typedef struct { + SilcClient client; + SilcClientConnection conn; + void *entry; + SilcIdType id_type; + char *fingerprint; +} *GetkeyContext; + +void silc_getkey_cb(bool success, void *context) +{ + GetkeyContext getkey = (GetkeyContext)context; + char *entity = (getkey->id_type == SILC_ID_CLIENT ? "user" : "server"); + char *name = (getkey->id_type == SILC_ID_CLIENT ? + ((SilcClientEntry)getkey->entry)->nickname : + ((SilcServerEntry)getkey->entry)->server_name); + + if (success) { + printformat_module("fe-common/silc", NULL, NULL, + MSGLEVEL_CRAP, SILCTXT_GETKEY_VERIFIED, entity, name); + } else { + printformat_module("fe-common/silc", NULL, NULL, + MSGLEVEL_CRAP, SILCTXT_GETKEY_DISCARD, entity, name); + } + + silc_free(getkey->fingerprint); + silc_free(getkey); +} + +/* Command reply handler. This function is called always in the command reply + function. If error occurs it will be called as well. Normal scenario + is that it will be called after the received command data has been parsed + and processed. The function is used to pass the received command data to + the application. + + `conn' is the associated client connection. `cmd_payload' is the command + payload data received from server and it can be ignored. It is provided + if the application would like to re-parse the received command data, + however, it must be noted that the data is parsed already by the library + thus the payload can be ignored. `success' is FALSE if error occured. + In this case arguments are not sent to the application. `command' is the + command reply being processed. The function has variable argument list + and each command defines the number and type of arguments it passes to the + application (on error they are not sent). */ + +void +silc_command_reply(SilcClient client, SilcClientConnection conn, + SilcCommandPayload cmd_payload, int success, + SilcCommand command, SilcCommandStatus status, ...) + +{ + SILC_SERVER_REC *server = conn->context; + SILC_CHANNEL_REC *chanrec; + va_list vp; + + va_start(vp, status); + + SILC_LOG_DEBUG(("Start")); + + switch(command) { + case SILC_COMMAND_WHOIS: + { + char buf[1024], *nickname, *username, *realname, *nick; + uint32 idle, mode; + SilcBuffer channels; + SilcClientEntry client_entry; + + if (status == SILC_STATUS_ERR_NO_SUCH_NICK || + status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) { + char *tmp; + tmp = silc_argument_get_arg_type(silc_command_get_args(cmd_payload), + 3, NULL); + if (tmp) + silc_say_error("%s: %s", tmp, + silc_client_command_status_message(status)); + break; + } + + if (!success) + return; + + client_entry = va_arg(vp, SilcClientEntry); + nickname = va_arg(vp, char *); + username = va_arg(vp, char *); + realname = va_arg(vp, char *); + channels = va_arg(vp, SilcBuffer); + mode = va_arg(vp, uint32); + idle = va_arg(vp, uint32); + + silc_parse_userfqdn(nickname, &nick, NULL); + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_WHOIS_USERINFO, nickname, + client_entry->username, client_entry->hostname, + nick, client_entry->nickname); + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_WHOIS_REALNAME, realname); + silc_free(nick); + + if (channels) { + SilcDList list = silc_channel_payload_parse_list(channels); + if (list) { + SilcChannelPayload entry; + memset(buf, 0, sizeof(buf)); + silc_dlist_start(list); + while ((entry = silc_dlist_get(list)) != SILC_LIST_END) { + char *m = silc_client_chumode_char(silc_channel_get_mode(entry)); + uint32 name_len; + char *name = silc_channel_get_name(entry, &name_len); + + if (m) + strncat(buf, m, strlen(m)); + strncat(buf, name, name_len); + strncat(buf, " ", 1); + silc_free(m); + } + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_WHOIS_CHANNELS, buf); + silc_channel_payload_list_free(list); + } + } + + if (mode) { + memset(buf, 0, sizeof(buf)); + + if ((mode & SILC_UMODE_SERVER_OPERATOR) || + (mode & SILC_UMODE_ROUTER_OPERATOR)) { + strcat(buf, (mode & SILC_UMODE_SERVER_OPERATOR) ? + "Server Operator " : + (mode & SILC_UMODE_ROUTER_OPERATOR) ? + "SILC Operator " : "[Unknown mode] "); + } + if (mode & SILC_UMODE_GONE) + strcat(buf, "away"); + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_WHOIS_MODES, buf); + } + + if (idle && nickname) { + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf) - 1, "%lu %s", + idle > 60 ? (idle / 60) : idle, + idle > 60 ? "minutes" : "seconds"); + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_WHOIS_IDLE, buf); + } + } + break; + + case SILC_COMMAND_WHOWAS: + { + char *nickname, *username, *realname; + + if (status == SILC_STATUS_ERR_NO_SUCH_NICK || + status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) { + char *tmp; + tmp = silc_argument_get_arg_type(silc_command_get_args(cmd_payload), + 3, NULL); + if (tmp) + silc_say_error("%s: %s", tmp, + silc_client_command_status_message(status)); + break; + } + + if (!success) + return; + + (void)va_arg(vp, SilcClientEntry); + nickname = va_arg(vp, char *); + username = va_arg(vp, char *); + realname = va_arg(vp, char *); + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_WHOWAS_USERINFO, nickname, username, + realname ? realname : ""); + } + break; + + case SILC_COMMAND_INVITE: + { + SilcChannelEntry channel; + char *invite_list; + SilcArgumentPayload args; + int argc = 0; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + invite_list = va_arg(vp, char *); + + args = silc_command_get_args(cmd_payload); + if (args) + argc = silc_argument_get_arg_num(args); + + if (invite_list) + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CHANNEL_INVITE_LIST, channel->channel_name, + invite_list); + else if (argc == 3) + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CHANNEL_NO_INVITE_LIST, + channel->channel_name); + } + break; + + case SILC_COMMAND_JOIN: + { + char *channel, *mode, *topic; + uint32 modei; + SilcChannelEntry channel_entry; + SilcBuffer client_id_list; + uint32 list_count; + + if (!success) + return; + + channel = va_arg(vp, char *); + channel_entry = va_arg(vp, SilcChannelEntry); + modei = va_arg(vp, uint32); + (void)va_arg(vp, uint32); + (void)va_arg(vp, unsigned char *); + (void)va_arg(vp, unsigned char *); + (void)va_arg(vp, unsigned char *); + topic = va_arg(vp, char *); + (void)va_arg(vp, unsigned char *); + list_count = va_arg(vp, uint32); + client_id_list = va_arg(vp, SilcBuffer); + + chanrec = silc_channel_find(server, channel); + if (!chanrec) + chanrec = silc_channel_create(server, channel, TRUE); + + if (topic) { + g_free_not_null(chanrec->topic); + chanrec->topic = *topic == '\0' ? NULL : g_strdup(topic); + signal_emit("channel topic changed", 1, chanrec); + } + + mode = silc_client_chmode(modei, + channel_entry->channel_key ? + channel_entry->channel_key->cipher->name : "", + channel_entry->hmac ? + silc_hmac_get_name(channel_entry->hmac) : ""); + g_free_not_null(chanrec->mode); + chanrec->mode = g_strdup(mode == NULL ? "" : mode); + signal_emit("channel mode changed", 1, chanrec); + + /* Resolve the client information */ + silc_client_get_clients_by_list(client, conn, list_count, client_id_list, + silc_client_join_get_users, + channel_entry); + break; + } + + case SILC_COMMAND_NICK: + { + SilcClientEntry client = va_arg(vp, SilcClientEntry); + char *old; + + if (!success) + return; + + old = g_strdup(server->nick); + server_change_nick(SERVER(server), client->nickname); + nicklist_rename_unique(SERVER(server), + server->conn->local_entry, server->nick, + client, client->nickname); + + signal_emit("message own_nick", 4, server, server->nick, old, ""); + g_free(old); + break; + } + + case SILC_COMMAND_LIST: + { + char *topic, *name; + int usercount; + char users[20]; + + if (!success) + return; + + (void)va_arg(vp, SilcChannelEntry); + name = va_arg(vp, char *); + topic = va_arg(vp, char *); + usercount = va_arg(vp, int); + + if (status == SILC_STATUS_LIST_START || + status == SILC_STATUS_OK) + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_LIST_HEADER); + + snprintf(users, sizeof(users) - 1, "%d", usercount); + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_LIST, + name, users, topic ? topic : ""); + } + break; + + case SILC_COMMAND_UMODE: + { + uint32 mode; + + if (!success) + return; + + mode = va_arg(vp, uint32); + + if (mode & SILC_UMODE_SERVER_OPERATOR) + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_SERVER_OPER); + + if (mode & SILC_UMODE_ROUTER_OPERATOR) + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_ROUTER_OPER); + } + break; + + case SILC_COMMAND_OPER: + if (!success) + return; + + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_SERVER_OPER); + break; + + case SILC_COMMAND_SILCOPER: + if (!success) + return; + + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_ROUTER_OPER); + break; + + case SILC_COMMAND_USERS: + { + SilcChannelEntry channel; + SilcChannelUser chu; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_CRAP, SILCTXT_USERS_HEADER, + channel->channel_name); + + silc_list_start(channel->clients); + while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) { + SilcClientEntry e = chu->client; + char stat[5], *mode; + + if (!e->nickname) + continue; + + memset(stat, 0, sizeof(stat)); + mode = silc_client_chumode_char(chu->mode); + if (e->mode & SILC_UMODE_GONE) + strcat(stat, "G"); + else + strcat(stat, "H"); + if (mode) + strcat(stat, mode); + + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_CRAP, SILCTXT_USERS, + e->nickname, stat, + e->username ? e->username : "", + e->hostname ? e->hostname : "", + e->realname ? e->realname : ""); + if (mode) + silc_free(mode); + } + } + break; + + case SILC_COMMAND_BAN: + { + SilcChannelEntry channel; + char *ban_list; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + ban_list = va_arg(vp, char *); + + if (ban_list) + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CHANNEL_BAN_LIST, channel->channel_name, + ban_list); + else + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CHANNEL_NO_BAN_LIST, + channel->channel_name); + } + break; + + case SILC_COMMAND_GETKEY: + { + SilcIdType id_type; + void *entry; + SilcPublicKey public_key; + unsigned char *pk; + uint32 pk_len; + GetkeyContext getkey; + + if (!success) + return; + + id_type = va_arg(vp, uint32); + entry = va_arg(vp, void *); + public_key = va_arg(vp, SilcPublicKey); + + if (public_key) { + pk = silc_pkcs_public_key_encode(public_key, &pk_len); + + getkey = silc_calloc(1, sizeof(*getkey)); + getkey->entry = entry; + getkey->id_type = id_type; + getkey->client = client; + getkey->conn = conn; + getkey->fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + + silc_verify_public_key_internal(client, conn, + (id_type == SILC_ID_CLIENT ? + SILC_SOCKET_TYPE_CLIENT : + SILC_SOCKET_TYPE_SERVER), + pk, pk_len, SILC_SKE_PK_TYPE_SILC, + silc_getkey_cb, getkey); + silc_free(pk); + } else { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_GETKEY_NOKEY); + } + } + break; + + case SILC_COMMAND_TOPIC: + { + SilcChannelEntry channel; + char *topic; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + topic = va_arg(vp, char *); + + if (topic) { + chanrec = silc_channel_find_entry(server, channel); + if (chanrec) { + g_free_not_null(chanrec->topic); + chanrec->topic = *topic == '\0' ? NULL : g_strdup(topic); + signal_emit("channel topic changed", 1, chanrec); + } + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_TOPIC, + channel->channel_name, topic); + } else { + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_TOPIC_NOT_SET, + channel->channel_name); + } + } + break; + + } + + va_end(vp); +} + +/* Internal routine to verify public key. If the `completion' is provided + it will be called to indicate whether public was verified or not. */ + +typedef struct { + SilcClient client; + SilcClientConnection conn; + char *filename; + char *entity; + unsigned char *pk; + uint32 pk_len; + SilcSKEPKType pk_type; + SilcVerifyPublicKey completion; + void *context; +} *PublicKeyVerify; + +static void verify_public_key_completion(const char *line, void *context) +{ + PublicKeyVerify verify = (PublicKeyVerify)context; + + if (line[0] == 'Y' || line[0] == 'y') { + /* Call the completion */ + if (verify->completion) + verify->completion(TRUE, verify->context); + + /* Save the key for future checking */ + silc_pkcs_save_public_key_data(verify->filename, verify->pk, + verify->pk_len, SILC_PKCS_FILE_PEM); + } else { + /* Call the completion */ + if (verify->completion) + verify->completion(FALSE, verify->context); + + printformat_module("fe-common/silc", NULL, NULL, + MSGLEVEL_CRAP, SILCTXT_PUBKEY_DISCARD, verify->entity); + } + + silc_free(verify->filename); + silc_free(verify->entity); + silc_free(verify->pk); + silc_free(verify); +} + +static void +silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context) +{ + int i; + char file[256], filename[256], *fingerprint, *babbleprint, *format; + struct passwd *pw; + struct stat st; + char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) ? + "server" : "client"); + PublicKeyVerify verify; + + if (pk_type != SILC_SKE_PK_TYPE_SILC) { + printformat_module("fe-common/silc", NULL, NULL, + MSGLEVEL_CRAP, SILCTXT_PUBKEY_UNSUPPORTED, + entity, pk_type); + if (completion) + completion(FALSE, context); + return; + } + + pw = getpwuid(getuid()); + if (!pw) { + if (completion) + completion(FALSE, context); + return; + } + + memset(filename, 0, sizeof(filename)); + memset(file, 0, sizeof(file)); + + if (conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) { + snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, + conn->sock->hostname, conn->sock->port); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + } else { + /* Replace all whitespaces with `_'. */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + for (i = 0; i < strlen(fingerprint); i++) + if (fingerprint[i] == ' ') + fingerprint[i] = '_'; + + snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + silc_free(fingerprint); + } + + /* Take fingerprint of the public key */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + babbleprint = silc_hash_babbleprint(NULL, pk, pk_len); + + verify = silc_calloc(1, sizeof(*verify)); + verify->client = client; + verify->conn = conn; + verify->filename = strdup(filename); + verify->entity = strdup(entity); + verify->pk = silc_calloc(pk_len, sizeof(*verify->pk)); + memcpy(verify->pk, pk, pk_len); + verify->pk_len = pk_len; + verify->pk_type = pk_type; + verify->completion = completion; + verify->context = context; + + /* Check whether this key already exists */ + if (stat(filename, &st) < 0) { + /* Key does not exist, ask user to verify the key and save it */ + + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_RECEIVED, entity); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_BABBLEPRINT, babbleprint); + format = format_get_text("fe-common/silc", NULL, NULL, NULL, + SILCTXT_PUBKEY_ACCEPT); + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + format, 0, verify); + g_free(format); + silc_free(fingerprint); + return; + } else { + /* The key already exists, verify it. */ + SilcPublicKey public_key; + unsigned char *encpk; + uint32 encpk_len; + + /* Load the key file */ + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_PEM)) + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_BIN)) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_RECEIVED, entity); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_BABBLEPRINT, babbleprint); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_COULD_NOT_LOAD, entity); + format = format_get_text("fe-common/silc", NULL, NULL, NULL, + SILCTXT_PUBKEY_ACCEPT_ANYWAY); + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + format, 0, verify); + g_free(format); + silc_free(fingerprint); + return; + } + + /* Encode the key data */ + encpk = silc_pkcs_public_key_encode(public_key, &encpk_len); + if (!encpk) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_RECEIVED, entity); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_BABBLEPRINT, babbleprint); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_MALFORMED, entity); + format = format_get_text("fe-common/silc", NULL, NULL, NULL, + SILCTXT_PUBKEY_ACCEPT_ANYWAY); + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + format, 0, verify); + g_free(format); + silc_free(fingerprint); + return; + } + + /* Compare the keys */ + if (memcmp(encpk, pk, encpk_len)) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_RECEIVED, entity); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_FINGERPRINT, entity, fingerprint); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_BABBLEPRINT, babbleprint); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_NO_MATCH, entity); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_MAYBE_EXPIRED, entity); + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_PUBKEY_MITM_ATTACK, entity); + + /* Ask user to verify the key and save it */ + format = format_get_text("fe-common/silc", NULL, NULL, NULL, + SILCTXT_PUBKEY_ACCEPT_ANYWAY); + keyboard_entry_redirect((SIGNAL_FUNC)verify_public_key_completion, + format, 0, verify); + g_free(format); + silc_free(fingerprint); + return; + } + + /* Local copy matched */ + if (completion) + completion(TRUE, context); + silc_free(fingerprint); + } +} + +/* Verifies received public key. The `conn_type' indicates which entity + (server, client etc.) has sent the public key. If user decides to trust + the key may be saved as trusted public key for later use. The + `completion' must be called after the public key has been verified. */ + +void +silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context) +{ + silc_verify_public_key_internal(client, conn, conn_type, pk, + pk_len, pk_type, + completion, context); +} + +/* Asks passphrase from user on the input line. */ + +typedef struct { + SilcAskPassphrase completion; + void *context; +} *AskPassphrase; + +void ask_passphrase_completion(const char *passphrase, void *context) +{ + AskPassphrase p = (AskPassphrase)context; + p->completion((unsigned char *)passphrase, + passphrase ? strlen(passphrase) : 0, p->context); + silc_free(p); +} + +void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context) +{ + AskPassphrase p = silc_calloc(1, sizeof(*p)); + p->completion = completion; + p->context = context; + + keyboard_entry_redirect((SIGNAL_FUNC)ask_passphrase_completion, + "Passphrase: ", ENTRY_REDIRECT_FLAG_HIDDEN, p); +} + +typedef struct { + SilcGetAuthMeth completion; + void *context; +} *InternalGetAuthMethod; + +/* Callback called when we've received the authentication method information + from the server after we've requested it. This will get the authentication + data from the user if needed. */ + +static void silc_get_auth_method_callback(SilcClient client, + SilcClientConnection conn, + SilcAuthMethod auth_meth, + void *context) +{ + InternalGetAuthMethod internal = (InternalGetAuthMethod)context; + + SILC_LOG_DEBUG(("Start")); + + switch (auth_meth) { + case SILC_AUTH_NONE: + /* No authentication required. */ + (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); + break; + case SILC_AUTH_PASSWORD: + /* Do not ask the passphrase from user, the library will ask it if + we do not provide it here. */ + (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); + break; + case SILC_AUTH_PUBLIC_KEY: + /* Do not get the authentication data now, the library will generate + it using our default key, if we do not provide it here. */ + /* XXX In the future when we support multiple local keys and multiple + local certificates we will need to ask from user which one to use. */ + (*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context); + break; + } + + silc_free(internal); +} + +/* Find authentication method and authentication data by hostname and + port. The hostname may be IP address as well. The found authentication + method and authentication data is returned to `auth_meth', `auth_data' + and `auth_data_len'. The function returns TRUE if authentication method + is found and FALSE if not. `conn' may be NULL. */ + +void silc_get_auth_method(SilcClient client, SilcClientConnection conn, + char *hostname, uint16 port, + SilcGetAuthMeth completion, void *context) +{ + InternalGetAuthMethod internal; + + SILC_LOG_DEBUG(("Start")); + + /* XXX must resolve from configuration whether this connection has + any specific authentication data */ + + /* If we do not have this connection configured by the user in a + configuration file then resolve the authentication method from the + server for this session. */ + internal = silc_calloc(1, sizeof(*internal)); + internal->completion = completion; + internal->context = context; + + silc_client_request_authentication_method(client, conn, + silc_get_auth_method_callback, + internal); +} + +/* Notifies application that failure packet was received. This is called + if there is some protocol active in the client. The `protocol' is the + protocol context. The `failure' is opaque pointer to the failure + indication. Note, that the `failure' is protocol dependant and application + must explicitly cast it to correct type. Usually `failure' is 32 bit + failure type (see protocol specs for all protocol failure types). */ + +void silc_failure(SilcClient client, SilcClientConnection conn, + SilcProtocol protocol, void *failure) +{ + SILC_LOG_DEBUG(("Start")); + + if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) { + SilcSKEStatus status = (SilcSKEStatus)failure; + + if (status == SILC_SKE_STATUS_BAD_VERSION) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_BAD_VERSION); + if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_UNSUPPORTED_PUBLIC_KEY); + if (status == SILC_SKE_STATUS_UNKNOWN_GROUP) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_UNKNOWN_GROUP); + if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_UNKNOWN_CIPHER); + if (status == SILC_SKE_STATUS_UNKNOWN_PKCS) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_UNKNOWN_PKCS); + if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_UNKNOWN_HASH_FUNCTION); + if (status == SILC_SKE_STATUS_UNKNOWN_HMAC) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_UNKNOWN_HMAC); + if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_INCORRECT_SIGNATURE); + if (status == SILC_SKE_STATUS_INVALID_COOKIE) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KE_INVALID_COOKIE); + } + + if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) { + uint32 err = (uint32)failure; + + if (err == SILC_AUTH_FAILED) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_AUTH_FAILED); + } +} + +/* Asks whether the user would like to perform the key agreement protocol. + This is called after we have received an key agreement packet or an + reply to our key agreement packet. This returns TRUE if the user wants + the library to perform the key agreement protocol and FALSE if it is not + desired (application may start it later by calling the function + silc_client_perform_key_agreement). */ + +int silc_key_agreement(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, const char *hostname, + uint16 port, SilcKeyAgreementCallback *completion, + void **context) +{ + char portstr[12]; + + SILC_LOG_DEBUG(("Start")); + + /* We will just display the info on the screen and return FALSE and user + will have to start the key agreement with a command. */ + + if (hostname) + snprintf(portstr, sizeof(portstr) - 1, "%d", port); + + if (!hostname) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_REQUEST, client_entry->nickname); + else + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_REQUEST_HOST, + client_entry->nickname, hostname, portstr); + + *completion = NULL; + *context = NULL; + + return FALSE; +} + +void silc_ftp(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, uint32 session_id, + const char *hostname, uint16 port) +{ + SILC_SERVER_REC *server; + char portstr[12]; + FtpSession ftp = silc_calloc(1, sizeof(*ftp)); + + SILC_LOG_DEBUG(("Start")); + + server = conn->context; + + ftp->client_entry = client_entry; + ftp->session_id = session_id; + ftp->send = FALSE; + ftp->conn = conn; + silc_dlist_add(server->ftp_sessions, ftp); + server->current_session = ftp; + + if (hostname) + snprintf(portstr, sizeof(portstr) - 1, "%d", port); + + if (!hostname) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_REQUEST, client_entry->nickname); + else + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_REQUEST_HOST, + client_entry->nickname, hostname, portstr); +} + +/* SILC client operations */ +SilcClientOperations ops = { + silc_say, + silc_channel_message, + silc_private_message, + silc_notify, + silc_command, + silc_command_reply, + silc_connect, + silc_disconnect, + silc_get_auth_method, + silc_verify_public_key, + silc_ask_passphrase, + silc_failure, + silc_key_agreement, + silc_ftp, +}; diff --git a/apps/irssi/src/silc/core/client_ops.h b/apps/irssi/src/silc/core/client_ops.h new file mode 100644 index 00000000..b7788ed7 --- /dev/null +++ b/apps/irssi/src/silc/core/client_ops.h @@ -0,0 +1,63 @@ +/* + + client_ops.h + + Author: Pekka Riikonen + + Copyright (C) 2001 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. + +*/ + +#ifndef CLIENT_OPS_H +#define CLIENT_OPS_H + +void silc_say(SilcClient client, SilcClientConnection conn, + SilcClientMessageType type, char *msg, ...); +void silc_say_error(char *msg, ...); +void silc_channel_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, + SilcChannelEntry channel, + SilcMessageFlags flags, char *msg); +void silc_private_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, + SilcMessageFlags flags, char *msg); +void silc_notify(SilcClient client, SilcClientConnection conn, + SilcNotifyType type, ...); +void silc_command(SilcClient client, SilcClientConnection conn, + SilcClientCommandContext cmd_context, int success, + SilcCommand command); +void silc_command_reply(SilcClient client, SilcClientConnection conn, + SilcCommandPayload cmd_payload, int success, + SilcCommand command, SilcCommandStatus status, ...); +void silc_connect(SilcClient client, SilcClientConnection conn, int success); +void silc_disconnect(SilcClient client, SilcClientConnection conn); +void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context); +void silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context); +void silc_get_auth_method(SilcClient client, SilcClientConnection conn, + char *hostname, uint16 port, + SilcGetAuthMeth completion, void *context); +void silc_failure(SilcClient client, SilcClientConnection conn, + SilcProtocol protocol, void *failure); +int silc_key_agreement(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, const char *hostname, + uint16 port, SilcKeyAgreementCallback *completion, + void **context); +void silc_ftp(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, uint32 session_id, + const char *hostname, uint16 port); + +#endif diff --git a/apps/irssi/src/silc/core/clientconfig.c b/apps/irssi/src/silc/core/clientconfig.c new file mode 100644 index 00000000..450020a3 --- /dev/null +++ b/apps/irssi/src/silc/core/clientconfig.c @@ -0,0 +1,846 @@ +/* + + serverconfig.c + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2001 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 "module.h" + +#include "net-nonblock.h" +#include "net-sendbuffer.h" +#include "signals.h" +#include "servers.h" +#include "commands.h" +#include "levels.h" +#include "modules.h" +#include "rawlog.h" +#include "misc.h" +#include "settings.h" + +#include "servers-setup.h" + +#include "silc-servers.h" +#include "silc-channels.h" +#include "silc-queries.h" +#include "window-item-def.h" + +#include "fe-common/core/printtext.h" + +/* + All possible configuration sections for SILC client. +*/ +SilcClientConfigSection silc_client_config_sections[] = { + { "[cipher]", + SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER, 4 }, + { "[pkcs]", + SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS, 1 }, + { "[hash]", + SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION, 4 }, + { "[hmac]", + SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC, 3 }, + { "[connection]", + SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION, 4 }, + + { NULL, SILC_CLIENT_CONFIG_SECTION_TYPE_NONE, 0 } +}; + +/* Allocates a new configuration object, opens configuration file and + parses the file. The parsed data is returned to the newly allocated + configuration object. */ + +SilcClientConfig silc_client_config_alloc(char *filename) +{ + SilcClientConfig new; + SilcBuffer buffer; + SilcClientConfigParse config_parse; + char *str; + + SILC_LOG_DEBUG(("Allocating new configuration object")); + + new = silc_calloc(1, sizeof(*new)); + new->filename = filename; + + /* Open configuration file and parse it */ + config_parse = NULL; + buffer = NULL; + str = convert_home(filename); + silc_config_open(str, &buffer); + g_free(str); + if (!buffer) + goto fail; + if ((silc_client_config_parse(new, buffer, &config_parse)) == FALSE) + goto fail; + if ((silc_client_config_parse_lines(new, config_parse)) == FALSE) + goto fail; + + silc_free(buffer); + + return new; + + fail: + silc_free(new); + return NULL; +} + +/* Free's a configuration object. */ + +void silc_client_config_free(SilcClientConfig config) +{ + if (config) { + + silc_free(config); + } +} + +/* Parses the the buffer and returns the parsed lines into return_config + argument. The return_config argument doesn't have to be initialized + before calling this. It will be initialized during the parsing. The + buffer sent as argument can be safely free'd after this function has + succesfully returned. */ + +int silc_client_config_parse(SilcClientConfig config, SilcBuffer buffer, + SilcClientConfigParse *return_config) +{ + int i, begin; + int linenum; + char line[1024], *cp; + SilcClientConfigSection *cptr = NULL; + SilcClientConfigParse parse = *return_config, first = NULL; + + SILC_LOG_DEBUG(("Parsing configuration file")); + + begin = 0; + linenum = 0; + while((begin = silc_gets(line, sizeof(line), + buffer->data, buffer->len, begin)) != EOF) { + cp = line; + linenum++; + + /* Check for bad line */ + if (silc_check_line(cp)) + continue; + + /* Remove tabs and whitespaces from the line */ + if (strchr(cp, '\t')) { + i = 0; + while(strchr(cp + i, '\t')) { + *strchr(cp + i, '\t') = ' '; + i++; + } + } + for (i = 0; i < strlen(cp); i++) { + if (cp[i] != ' ') { + if (i) + cp++; + break; + } + cp++; + } + + /* Parse line */ + switch(cp[0]) { + case '[': + /* + * Start of a section + */ + + /* Remove new line sign */ + if (strchr(cp, '\n')) + *strchr(cp, '\n') = '\0'; + + /* Check for matching sections */ + for (cptr = silc_client_config_sections; cptr->section; cptr++) + if (!strncasecmp(cp, cptr->section, strlen(cptr->section))) + break; + + if (!cptr->section) { + fprintf(stderr, "%s:%d: Unknown section `%s'\n", + config->filename, linenum, cp); + return FALSE; + } + + break; + default: + /* + * Start of a configuration line + */ + + if (!cptr) { + fprintf(stderr, "%s:%d: Unknown start of a section `%s'\n", + config->filename, linenum, cp); + return FALSE; + } + + /* Handle config section */ + if (cptr->type != SILC_CLIENT_CONFIG_SECTION_TYPE_NONE) { + + if (strchr(cp, '\n')) + *strchr(cp, '\n') = ':'; + + if (parse == NULL) { + parse = silc_calloc(1, sizeof(*parse)); + parse->line = NULL; + parse->section = NULL; + parse->next = NULL; + parse->prev = NULL; + } else { + if (parse->next == NULL) { + parse->next = silc_calloc(1, sizeof(*parse->next)); + parse->next->line = NULL; + parse->next->section = NULL; + parse->next->next = NULL; + parse->next->prev = parse; + parse = parse->next; + } + } + + if (first == NULL) + first = parse; + + /* Add the line to parsing structure for further parsing. */ + if (parse) { + parse->section = cptr; + parse->line = silc_buffer_alloc(strlen(cp) + 1); + parse->linenum = linenum; + silc_buffer_pull_tail(parse->line, strlen(cp)); + silc_buffer_put(parse->line, cp, strlen(cp)); + } + } + break; + } + } + + /* Set the return_config argument to its first value so that further + parsing can be started from the first line. */ + *return_config = first; + + return TRUE; +} + +/* Parses the lines earlier read from configuration file. The config object + must not be initialized, it will be initialized in this function. The + parse_config argument is uninitialized automatically during this + function. */ + +int silc_client_config_parse_lines(SilcClientConfig config, + SilcClientConfigParse parse_config) +{ + int ret, check = FALSE; + char *tmp; + SilcClientConfigParse pc = parse_config; + SilcBuffer line; + + SILC_LOG_DEBUG(("Parsing configuration lines")); + + if (!config) + return FALSE; + + while(pc) { + check = FALSE; + line = pc->line; + + /* Get number of tokens in line (command section is handeled + specially and has no tokens at all). */ + ret = silc_config_check_num_token(line); + if (ret != pc->section->maxfields) { + /* Bad line */ + fprintf(stderr, "%s:%d: Missing tokens, %d tokens (should be %d)\n", + config->filename, pc->linenum, ret, + pc->section->maxfields); + break; + } + + /* Parse the line */ + switch(pc->section->type) { + case SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER: + + if (!config->cipher) { + config->cipher = silc_calloc(1, sizeof(*config->cipher)); + config->cipher->next = NULL; + config->cipher->prev = NULL; + } else { + if (!config->cipher->next) { + config->cipher->next = + silc_calloc(1, sizeof(*config->cipher->next)); + config->cipher->next->next = NULL; + config->cipher->next->prev = config->cipher; + config->cipher = config->cipher->next; + } + } + + /* Get cipher name */ + ret = silc_config_get_token(line, &config->cipher->alg_name); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Cipher name not defined\n", + config->filename, pc->linenum); + break; + } + + /* Get module name */ + config->cipher->sim_name = NULL; + ret = silc_config_get_token(line, &config->cipher->sim_name); + if (ret < 0) + break; + + /* Get key length */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Cipher key length not defined\n", + config->filename, pc->linenum); + break; + } + config->cipher->key_len = atoi(tmp); + silc_free(tmp); + + /* Get block length */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Cipher block length not defined\n", + config->filename, pc->linenum); + break; + } + config->cipher->block_len = atoi(tmp); + silc_free(tmp); + + check = TRUE; + break; + + case SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS: + + if (!config->pkcs) { + config->pkcs = silc_calloc(1, sizeof(*config->pkcs)); + config->pkcs->next = NULL; + config->pkcs->prev = NULL; + } else { + if (!config->pkcs->next) { + config->pkcs->next = + silc_calloc(1, sizeof(*config->pkcs->next)); + config->pkcs->next->next = NULL; + config->pkcs->next->prev = config->pkcs; + config->pkcs = config->pkcs->next; + } + } + + /* Get PKCS name */ + ret = silc_config_get_token(line, &config->pkcs->alg_name); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: PKCS name not defined\n", + config->filename, pc->linenum); + break; + } + + check = TRUE; + break; + + case SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION: + + if (!config->hash_func) { + config->hash_func = silc_calloc(1, sizeof(*config->hash_func)); + config->hash_func->next = NULL; + config->hash_func->prev = NULL; + } else { + if (!config->hash_func->next) { + config->hash_func->next = + silc_calloc(1, sizeof(*config->hash_func->next)); + config->hash_func->next->next = NULL; + config->hash_func->next->prev = config->hash_func; + config->hash_func = config->hash_func->next; + } + } + + /* Get Hash function name */ + ret = silc_config_get_token(line, &config->hash_func->alg_name); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Hash function name not defined\n", + config->filename, pc->linenum); + break; + } + + /* Get Hash function module name */ + config->hash_func->sim_name = NULL; + ret = silc_config_get_token(line, &config->hash_func->sim_name); + if (ret < 0) + break; + + /* Get block length */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Hash function block length not defined\n", + config->filename, pc->linenum); + break; + } + config->hash_func->block_len = atoi(tmp); + silc_free(tmp); + + /* Get hash length */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Hash function hash length not defined\n", + config->filename, pc->linenum); + break; + } + config->hash_func->key_len = atoi(tmp); + silc_free(tmp); + + check = TRUE; + break; + + case SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC: + + if (!config->hmac) { + config->hmac = silc_calloc(1, sizeof(*config->hmac)); + config->hmac->next = NULL; + config->hmac->prev = NULL; + } else { + if (!config->hmac->next) { + config->hmac->next = + silc_calloc(1, sizeof(*config->hmac->next)); + config->hmac->next->next = NULL; + config->hmac->next->prev = config->hmac; + config->hmac = config->hmac->next; + } + } + + /* Get HMAC name */ + ret = silc_config_get_token(line, &config->hmac->alg_name); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: HMAC name not defined\n", + config->filename, pc->linenum); + break; + } + + /* Get Hash function name */ + ret = silc_config_get_token(line, &config->hmac->sim_name); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Hash function name not defined\n", + config->filename, pc->linenum); + break; + } + + /* Get MAC length */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: HMAC's MAC length not defined\n", + config->filename, pc->linenum); + break; + } + config->hmac->key_len = atoi(tmp); + silc_free(tmp); + + check = TRUE; + break; + + case SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION: + + if (!config->conns) { + config->conns = silc_calloc(1, sizeof(*config->conns)); + config->conns->next = NULL; + config->conns->prev = NULL; + } else { + if (!config->conns->next) { + config->conns->next = silc_calloc(1, sizeof(*config->conns)); + config->conns->next->next = NULL; + config->conns->next->prev = config->conns; + config->conns = config->conns->next; + } + } + + /* Get host */ + ret = silc_config_get_token(line, &config->conns->host); + if (ret < 0) + break; + if (ret == 0) + /* Any host */ + config->conns->host = strdup("*"); + + /* Get authentication method */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret) { + if (strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PASSWD) && + strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY)) { + fprintf(stderr, "%s:%d: Unknown authentication method '%s'\n", + config->filename, pc->linenum, tmp); + break; + } + + if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PASSWD)) + config->conns->auth_meth = SILC_AUTH_PASSWORD; + + if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY)) + config->conns->auth_meth = SILC_AUTH_PUBLIC_KEY; + + silc_free(tmp); + } + + /* Get authentication data */ + ret = silc_config_get_token(line, &config->conns->auth_data); + if (ret < 0) + break; + + /* Get port */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret) { + config->conns->port = atoi(tmp); + silc_free(tmp); + } + + check = TRUE; + break; + + case SILC_CLIENT_CONFIG_SECTION_TYPE_NONE: + default: + break; + } + + /* Check for error */ + if (check == FALSE) { + /* Line could not be parsed */ + fprintf(stderr, "%s:%d: Parse error\n", config->filename, pc->linenum); + break; + } + + pc = pc->next; + } + + if (check == FALSE) + return FALSE;; + + /* Before returning all the lists in the config object must be set + to their first values (the last value is first here). */ + while (config->cipher && config->cipher->prev) + config->cipher = config->cipher->prev; + while (config->pkcs && config->pkcs->prev) + config->pkcs = config->pkcs->prev; + while (config->hash_func && config->hash_func->prev) + config->hash_func = config->hash_func->prev; + while (config->hmac && config->hmac->prev) + config->hmac = config->hmac->prev; + while (config->conns && config->conns->prev) + config->conns = config->conns->prev; + + SILC_LOG_DEBUG(("Done")); + + return TRUE; +} + +/* Registers configured ciphers. These can then be allocated by the + client when needed. */ + +bool silc_client_config_register_ciphers(SilcClientConfig config) +{ + SilcClientConfigSectionAlg *alg; + SilcClient client = config->client; + + SILC_LOG_DEBUG(("Registering configured ciphers")); + + if (!config->cipher) + return FALSE; + + alg = config->cipher; + while(alg) { + + if (!alg->sim_name) { + /* Crypto module is supposed to be built in. Get the pointer to the + built in cipher and register it. */ + int i; + + for (i = 0; silc_default_ciphers[i].name; i++) + if (!strcmp(silc_default_ciphers[i].name, alg->alg_name)) { + silc_cipher_register(&silc_default_ciphers[i]); + break; + } + + if (!silc_cipher_is_supported(alg->alg_name)) { + SILC_LOG_ERROR(("Unknown cipher `%s'", alg->alg_name)); + silc_client_stop(client); + exit(1); + } + +#ifdef SILC_SIM + } else { + /* Load (try at least) the crypto SIM module */ + SilcCipherObject cipher; + SilcSimContext *sim; + char *alg_name; + + memset(&cipher, 0, sizeof(cipher)); + cipher.name = alg->alg_name; + cipher.block_len = alg->block_len; + cipher.key_len = alg->key_len * 8; + + sim = silc_sim_alloc(); + sim->type = SILC_SIM_CIPHER; + sim->libname = alg->sim_name; + + alg_name = strdup(alg->alg_name); + if (strchr(alg_name, '-')) + *strchr(alg_name, '-') = '\0'; + + if ((silc_sim_load(sim))) { + cipher.set_key = + silc_sim_getsym(sim, silc_sim_symname(alg_name, + SILC_CIPHER_SIM_SET_KEY)); + SILC_LOG_DEBUG(("set_key=%p", cipher.set_key)); + cipher.set_key_with_string = + silc_sim_getsym(sim, silc_sim_symname(alg_name, + SILC_CIPHER_SIM_SET_KEY_WITH_STRING)); + SILC_LOG_DEBUG(("set_key_with_string=%p", cipher.set_key_with_string)); + cipher.encrypt = + silc_sim_getsym(sim, silc_sim_symname(alg_name, + SILC_CIPHER_SIM_ENCRYPT_CBC)); + SILC_LOG_DEBUG(("encrypt_cbc=%p", cipher.encrypt)); + cipher.decrypt = + silc_sim_getsym(sim, silc_sim_symname(alg_name, + SILC_CIPHER_SIM_DECRYPT_CBC)); + SILC_LOG_DEBUG(("decrypt_cbc=%p", cipher.decrypt)); + cipher.context_len = + silc_sim_getsym(sim, silc_sim_symname(alg_name, + SILC_CIPHER_SIM_CONTEXT_LEN)); + SILC_LOG_DEBUG(("context_len=%p", cipher.context_len)); + + /* Put the SIM to the table of all SIM's in client */ + sims = silc_realloc(sims, + sizeof(*sims) * + (sims_count + 1)); + sims[sims_count] = sim; + sims_count++; + + silc_free(alg_name); + } else { + SILC_LOG_ERROR(("Error configuring ciphers")); + silc_client_stop(client); + exit(1); + } + + /* Register the cipher */ + silc_cipher_register(&cipher); +#endif + } + + alg = alg->next; + } + + return TRUE; +} + +/* Registers configured PKCS's. */ + +bool silc_client_config_register_pkcs(SilcClientConfig config) +{ + SilcClientConfigSectionAlg *alg = config->pkcs; + SilcClient client = config->client; + + SILC_LOG_DEBUG(("Registering configured PKCS")); + + if (!alg) + return FALSE; + + while(alg) { + int i; + + for (i = 0; silc_default_pkcs[i].name; i++) + if (!strcmp(silc_default_pkcs[i].name, alg->alg_name)) { + silc_pkcs_register(&silc_default_pkcs[i]); + break; + } + + if (!silc_pkcs_is_supported(alg->alg_name)) { + SILC_LOG_ERROR(("Unknown PKCS `%s'", alg->alg_name)); + silc_client_stop(client); + exit(1); + } + + alg = alg->next; + } + + return TRUE; +} + +/* Registers configured hash funtions. These can then be allocated by the + client when needed. */ + +bool silc_client_config_register_hashfuncs(SilcClientConfig config) +{ + SilcClientConfigSectionAlg *alg; + SilcClient client = config->client; + + SILC_LOG_DEBUG(("Registering configured hash functions")); + + if (!config->hash_func) + return FALSE; + + alg = config->hash_func; + while(alg) { + if (!alg->sim_name) { + int i; + + for (i = 0; silc_default_hash[i].name; i++) + if (!strcmp(silc_default_hash[i].name, alg->alg_name)) { + silc_hash_register(&silc_default_hash[i]); + break; + } + + if (!silc_hash_is_supported(alg->alg_name)) { + SILC_LOG_ERROR(("Unknown hash function `%s'", alg->alg_name)); + silc_client_stop(client); + exit(1); + } +#ifdef SILC_SIM + } else { + /* Load (try at least) the hash SIM module */ + SilcHashObject hash; + SilcSimContext *sim; + + memset(&hash, 0, sizeof(hash)); + hash.name = alg->alg_name; + hash.block_len = alg->block_len; + hash.hash_len = alg->key_len; + + sim = silc_sim_alloc(); + sim->type = SILC_SIM_HASH; + sim->libname = alg->sim_name; + + if ((silc_sim_load(sim))) { + hash.init = + silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + SILC_HASH_SIM_INIT)); + SILC_LOG_DEBUG(("init=%p", hash.init)); + hash.update = + silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + SILC_HASH_SIM_UPDATE)); + SILC_LOG_DEBUG(("update=%p", hash.update)); + hash.final = + silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + SILC_HASH_SIM_FINAL)); + SILC_LOG_DEBUG(("final=%p", hash.final)); + hash.context_len = + silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + SILC_HASH_SIM_CONTEXT_LEN)); + SILC_LOG_DEBUG(("context_len=%p", hash.context_len)); + + /* Put the SIM to the table of all SIM's in client */ + sims = silc_realloc(sims, + sizeof(*sims) * + (sims_count + 1)); + sims[sims_count] = sim; + sims_count++; + } else { + SILC_LOG_ERROR(("Error configuring hash functions")); + silc_client_stop(client); + exit(1); + } + + /* Register the hash function */ + silc_hash_register(&hash); +#endif + } + alg = alg->next; + } + + return TRUE; +} + +/* Registers configured HMACs. These can then be allocated by the + client when needed. */ + +bool silc_client_config_register_hmacs(SilcClientConfig config) +{ + SilcClientConfigSectionAlg *alg; + SilcClient client = config->client; + + SILC_LOG_DEBUG(("Registering configured HMACs")); + + if (!config->hmac) + return FALSE; + + alg = config->hmac; + while(alg) { + SilcHmacObject hmac; + + if (!silc_hash_is_supported(alg->sim_name)) { + SILC_LOG_ERROR(("Unknown hash function `%s' for HMAC `%s'", + alg->sim_name, alg->alg_name)); + silc_client_stop(client); + exit(1); + } + + /* Register the HMAC */ + memset(&hmac, 0, sizeof(hmac)); + hmac.name = alg->alg_name; + hmac.len = alg->key_len; + silc_hmac_register(&hmac); + + alg = alg->next; + } + + return TRUE; +} + +SilcClientConfigSectionConnection * +silc_client_config_find_connection(SilcClientConfig config, + char *host, int port) +{ + int i; + SilcClientConfigSectionConnection *conn = NULL; + + SILC_LOG_DEBUG(("Finding connection")); + + if (!host) + return NULL; + + if (!config->conns) + return NULL; + + conn = config->conns; + for (i = 0; conn; i++) { + if (silc_string_compare(conn->host, host)) + break; + conn = conn->next; + } + + if (!conn) + return NULL; + + SILC_LOG_DEBUG(("Found match")); + + return conn; +} diff --git a/apps/irssi/src/silc/core/clientconfig.h b/apps/irssi/src/silc/core/clientconfig.h new file mode 100644 index 00000000..a9a7f19e --- /dev/null +++ b/apps/irssi/src/silc/core/clientconfig.h @@ -0,0 +1,118 @@ +/* + + clientconfig.h + + 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. + +*/ + +#ifndef CLIENTCONFIG_H +#define CLIENTCONFIG_H + +/* Holds information of configured algorithms */ +typedef struct SilcClientConfigSectionAlgStruct { + char *alg_name; + char *sim_name; + uint32 block_len; + uint32 key_len; + struct SilcClientConfigSectionAlgStruct *next; + struct SilcClientConfigSectionAlgStruct *prev; +#define SILC_CLIENT_CONFIG_MODNAME "builtin" +} SilcClientConfigSectionAlg; + +/* Holds all server connections from config file */ +typedef struct SilcClientConfigSectionConnectionStruct { + char *host; + int auth_meth; + char *auth_data; + uint16 port; + struct SilcClientConfigSectionConnectionStruct *next; + struct SilcClientConfigSectionConnectionStruct *prev; +#define SILC_CLIENT_CONFIG_AUTH_METH_PASSWD "passwd" +#define SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY "pubkey" +} SilcClientConfigSectionConnection; + +/* + SILC Client Config object. + + This object holds all the data parsed from the SILC client configuration + file. This is mainly used at the initialization of the client. + +*/ +typedef struct { + /* Pointer back to the client */ + void *client; + + /* Filename of the configuration file */ + char *filename; + + /* Configuration sections */ + SilcClientConfigSectionAlg *cipher; + SilcClientConfigSectionAlg *pkcs; + SilcClientConfigSectionAlg *hash_func; + SilcClientConfigSectionAlg *hmac; + SilcClientConfigSectionConnection *conns; +} SilcClientConfigObject; + +typedef SilcClientConfigObject *SilcClientConfig; + +/* Configuration section type enumerations. */ +typedef enum { + SILC_CLIENT_CONFIG_SECTION_TYPE_NONE = 0, + SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER, + SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS, + SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION, + SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC, + SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION, +} SilcClientConfigSectionType; + +/* SILC Configuration Section structure. */ +typedef struct { + const char *section; + SilcClientConfigSectionType type; + int maxfields; +} SilcClientConfigSection; + +/* List of all possible config sections in SILC client */ +extern SilcClientConfigSection silc_client_config_sections[]; + +/* Structure used in parsing the configuration lines. The line is read + from a file to this structure before parsing it further. */ +typedef struct SilcClientConfigParseStruct { + SilcBuffer line; + int linenum; + SilcClientConfigSection *section; + struct SilcClientConfigParseStruct *next; + struct SilcClientConfigParseStruct *prev; +} *SilcClientConfigParse; + +/* Prototypes */ +SilcClientConfig silc_client_config_alloc(char *filename); +void silc_client_config_free(SilcClientConfig config); +int silc_client_config_parse(SilcClientConfig config, SilcBuffer buffer, + SilcClientConfigParse *return_config); +int silc_client_config_parse_lines(SilcClientConfig config, + SilcClientConfigParse parse_config); +int silc_client_config_check_sections(uint32 checkmask); +void silc_client_config_setlogfiles(SilcClientConfig config); +bool silc_client_config_register_ciphers(SilcClientConfig config); +bool silc_client_config_register_pkcs(SilcClientConfig config); +bool silc_client_config_register_hashfuncs(SilcClientConfig config); +bool silc_client_config_register_hmacs(SilcClientConfig config); +SilcClientConfigSectionConnection * +silc_client_config_find_connection(SilcClientConfig config, + char *host, int port); + +#endif diff --git a/apps/irssi/src/silc/core/clientutil.c b/apps/irssi/src/silc/core/clientutil.c new file mode 100644 index 00000000..622fb762 --- /dev/null +++ b/apps/irssi/src/silc/core/clientutil.c @@ -0,0 +1,605 @@ +/* + + 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 "module.h" + +#include "net-nonblock.h" +#include "net-sendbuffer.h" +#include "signals.h" +#include "servers.h" +#include "commands.h" +#include "levels.h" +#include "modules.h" +#include "rawlog.h" +#include "misc.h" +#include "settings.h" + +#include "channels-setup.h" + +#include "silc-servers.h" +#include "silc-channels.h" +#include "silc-queries.h" +#include "silc-nicklist.h" +#include "window-item-def.h" + +#include "fe-common/core/printtext.h" +#include "fe-common/core/keyboard.h" + +/* Lists supported ciphers */ + +void silc_client_list_ciphers() +{ + char *ciphers = silc_cipher_get_supported(); + fprintf(stdout, "%s\n", ciphers); + silc_free(ciphers); +} + +/* Lists supported hash functions */ + +void silc_client_list_hash_funcs() +{ + char *hash = silc_hash_get_supported(); + fprintf(stdout, "%s\n", hash); + silc_free(hash); +} + +/* Lists supported hash functions */ + +void silc_client_list_hmacs() +{ + char *hash = silc_hmac_get_supported(); + fprintf(stdout, "%s\n", hash); + silc_free(hash); +} + +/* Lists supported PKCS algorithms */ + +void silc_client_list_pkcs() +{ + char *pkcs = silc_pkcs_get_supported(); + fprintf(stdout, "%s\n", pkcs); + silc_free(pkcs); +} + +/* Displays input prompt on command line and takes input data from user */ + +char *silc_client_get_input(const char *prompt) +{ + char input[2048]; + int fd; + + fd = open("/dev/tty", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "silc: %s\n", strerror(errno)); + return NULL; + } + + memset(input, 0, sizeof(input)); + + printf("%s", prompt); + fflush(stdout); + + if ((read(fd, input, sizeof(input))) < 0) { + fprintf(stderr, "silc: %s\n", strerror(errno)); + return NULL; + } + + if (strlen(input) <= 1) + return NULL; + + if (strchr(input, '\n')) + *strchr(input, '\n') = '\0'; + + return strdup(input); +} + +/* Returns identifier string for public key generation. */ + +char *silc_client_create_identifier() +{ + char *username = NULL, *realname = NULL; + char *hostname, email[256]; + char *ident; + + /* Get realname */ + realname = silc_get_real_name(); + + /* Get hostname */ + hostname = silc_net_localhost(); + if (!hostname) + return NULL; + + /* Get username (mandatory) */ + username = silc_get_username(); + if (!username) + return NULL; + + /* Create default email address, whether it is right or not */ + snprintf(email, sizeof(email), "%s@%s", username, hostname); + + ident = silc_pkcs_encode_identifier(username, hostname, realname, email, + NULL, NULL); + if (realname) + silc_free(realname); + silc_free(hostname); + silc_free(username); + + return ident; +} + +/* Creates new public key and private key pair. This is used only + when user wants to create new key pair from command line. */ + +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) +{ + SilcPKCS pkcs; + SilcPublicKey pub_key; + SilcPrivateKey prv_key; + SilcRng rng; + unsigned char *key; + uint32 key_len; + char line[256]; + char *pkfile = NULL, *prvfile = NULL; + + if (!pkcs_name || !public_key || !private_key) + printf("\ +New pair of keys will be created. Please, answer to following questions.\n\ +"); + + if (!pkcs_name) { + again_name: + pkcs_name = + silc_client_get_input("PKCS name (l to list names) [rsa]: "); + if (!pkcs_name) + pkcs_name = strdup("rsa"); + + if (*pkcs_name == 'l' || *pkcs_name == 'L') { + silc_client_list_pkcs(); + silc_free(pkcs_name); + goto again_name; + } + } + + if (!silc_pkcs_is_supported(pkcs_name)) { + fprintf(stderr, "Unknown PKCS `%s'", pkcs_name); + return FALSE; + } + + if (!bits) { + char *length = NULL; + length = + silc_client_get_input("Key length in bits [1024]: "); + if (!length) + bits = 1024; + else + bits = atoi(length); + } + + if (!identifier) { + char *def = silc_client_create_identifier(); + + memset(line, 0, sizeof(line)); + if (def) + snprintf(line, sizeof(line), "Identifier [%s]: ", def); + else + snprintf(line, sizeof(line), + "Identifier (eg. UN=jon, HN=jon.dummy.com, " + "RN=Jon Johnson, E=jon@dummy.com): "); + + while (!identifier) { + identifier = silc_client_get_input(line); + if (!identifier && def) + identifier = strdup(def); + } + + if (def) + silc_free(def); + } + + rng = silc_rng_alloc(); + silc_rng_init(rng); + silc_rng_global_init(rng); + + if (!public_key) { + memset(line, 0, sizeof(line)); + snprintf(line, sizeof(line), "Public key filename [%s] ", + SILC_CLIENT_PUBLIC_KEY_NAME); + pkfile = silc_client_get_input(line); + if (!pkfile) + pkfile = SILC_CLIENT_PUBLIC_KEY_NAME; + } else { + pkfile = public_key; + } + + if (!private_key) { + memset(line, 0, sizeof(line)); + snprintf(line, sizeof(line), "Public key filename [%s] ", + SILC_CLIENT_PRIVATE_KEY_NAME); + prvfile = silc_client_get_input(line); + if (!prvfile) + prvfile = SILC_CLIENT_PRIVATE_KEY_NAME; + } else { + prvfile = private_key; + } + + /* Generate keys */ + silc_pkcs_alloc(pkcs_name, &pkcs); + pkcs->pkcs->init(pkcs->context, bits, rng); + + /* Save public key into file */ + key = silc_pkcs_get_public_key(pkcs, &key_len); + pub_key = silc_pkcs_public_key_alloc(pkcs->pkcs->name, identifier, + key, key_len); + silc_pkcs_save_public_key(pkfile, pub_key, SILC_PKCS_FILE_PEM); + if (ret_pub_key) + *ret_pub_key = pub_key; + + memset(key, 0, sizeof(key_len)); + silc_free(key); + + /* Save private key into file */ + key = silc_pkcs_get_private_key(pkcs, &key_len); + prv_key = silc_pkcs_private_key_alloc(pkcs->pkcs->name, key, key_len); + + silc_pkcs_save_private_key(prvfile, prv_key, NULL, SILC_PKCS_FILE_BIN); + if (ret_prv_key) + *ret_prv_key = prv_key; + + printf("Public key has been saved into `%s'.\n", pkfile); + printf("Private key has been saved into `%s'.\n", prvfile); + printf("Press to continue...\n"); + getchar(); + + memset(key, 0, sizeof(key_len)); + silc_free(key); + + 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], clientfilename[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); + snprintf(clientfilename, sizeof(clientfilename) - 1, "%s/.silc/clientkeys", + 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; + } + +#if 0 + /* 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; + } + } +#endif + } + + /* + * 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 ~./silc/clientkeys directory + */ + if ((stat(clientfilename, &st)) == -1) { + /* If dir doesn't exist */ + if (errno == ENOENT) { + if (pw->pw_uid == geteuid()) { + if ((mkdir(clientfilename, 0755)) == -1) { + fprintf(stderr, "Couldn't create `%s' directory\n", clientfilename); + return FALSE; + } + } else { + fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n", + clientfilename); + 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; + + silc_pkcs_alloc(client->public_key->name, &client->pkcs); + silc_pkcs_public_key_set(client->pkcs, client->public_key); + silc_pkcs_private_key_set(client->pkcs, client->private_key); + + return TRUE; +} + +/* Dumps the public key on screen. Used from the command line option. */ + +int silc_client_show_key(char *keyfile) +{ + SilcPublicKey public_key; + SilcPublicKeyIdentifier ident; + char *fingerprint, *babbleprint; + unsigned char *pk; + uint32 pk_len; + SilcPKCS pkcs; + int key_len = 0; + + if (silc_pkcs_load_public_key(keyfile, &public_key, + SILC_PKCS_FILE_PEM) == FALSE) + if (silc_pkcs_load_public_key(keyfile, &public_key, + SILC_PKCS_FILE_BIN) == FALSE) { + fprintf(stderr, "Could not load public key file `%s'\n", keyfile); + return FALSE; + } + + ident = silc_pkcs_decode_identifier(public_key->identifier); + + pk = silc_pkcs_public_key_encode(public_key, &pk_len); + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + babbleprint = silc_hash_babbleprint(NULL, pk, pk_len); + + if (silc_pkcs_alloc(public_key->name, &pkcs)) { + key_len = silc_pkcs_public_key_set(pkcs, public_key); + silc_pkcs_free(pkcs); + } + + printf("Public key file : %s\n", keyfile); + printf("Algorithm : %s\n", public_key->name); + if (key_len) + printf("Key length (bits) : %d\n", key_len); + if (ident->realname) + printf("Real name : %s\n", ident->realname); + if (ident->username) + printf("Username : %s\n", ident->username); + if (ident->host) + printf("Hostname : %s\n", ident->host); + if (ident->email) + printf("Email : %s\n", ident->email); + if (ident->org) + printf("Organization : %s\n", ident->org); + if (ident->country) + printf("Country : %s\n", ident->country); + printf("Fingerprint (SHA1) : %s\n", fingerprint); + printf("Babbleprint (SHA1) : %s\n", babbleprint); + + fflush(stdout); + + silc_free(fingerprint); + silc_free(pk); + silc_pkcs_public_key_free(public_key); + silc_pkcs_free_identifier(ident); + + return TRUE; +} diff --git a/lib/silccrypt/loki_internal.h b/apps/irssi/src/silc/core/clientutil.h similarity index 51% rename from lib/silccrypt/loki_internal.h rename to apps/irssi/src/silc/core/clientutil.h index 650f6b44..efd69da0 100644 --- a/lib/silccrypt/loki_internal.h +++ b/apps/irssi/src/silc/core/clientutil.h @@ -1,6 +1,6 @@ /* - loki_internal.h + client.h Author: Pekka Riikonen @@ -18,20 +18,25 @@ */ -#ifndef LOKI_INTERNAL_H -#define LOKI_INTERNAL_H +#ifndef CLIENTUTIL_H +#define CLIENTUTIL_H -/* Cipher's context */ -typedef struct { - u4byte l_key[96]; -} LokiContext; +#include "signals.h" /* Prototypes */ -u4byte *loki_set_key(LokiContext *ctx, - const u4byte in_key[], const u4byte key_len); -void loki_encrypt(LokiContext *ctx, - const u4byte in_blk[4], u4byte out_blk[4]); -void loki_decrypt(LokiContext *ctx, - const u4byte in_blk[4], u4byte out_blk[4]); +char *silc_client_get_input(const char *prompt); +void silc_client_list_ciphers(); +void silc_client_list_hash_funcs(); +void silc_client_list_hmacs(); +void silc_client_list_pkcs(); +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); +int silc_client_show_key(char *keyfile); #endif diff --git a/apps/irssi/src/silc/core/module.h b/apps/irssi/src/silc/core/module.h new file mode 100644 index 00000000..b3c19a49 --- /dev/null +++ b/apps/irssi/src/silc/core/module.h @@ -0,0 +1,12 @@ +#include "common.h" + +#define MODULE_NAME "silc" + +#undef PACKAGE +#undef VERSION +#include "silcincludes.h" +#include "clientlibincludes.h" +#include "client_ops.h" +#include "silc-core.h" + +#define SILC_PROTOCOL (chat_protocol_lookup("SILC")) diff --git a/apps/irssi/src/silc/core/silc-channels.c b/apps/irssi/src/silc/core/silc-channels.c new file mode 100644 index 00000000..c02d61d2 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-channels.c @@ -0,0 +1,1321 @@ +/* + silc-channels.c : irssi + + Copyright (C) 2000 - 2001 Timo Sirainen + Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "net-nonblock.h" +#include "net-sendbuffer.h" +#include "signals.h" +#include "servers.h" +#include "commands.h" +#include "levels.h" +#include "modules.h" +#include "rawlog.h" +#include "misc.h" +#include "settings.h" + +#include "channels-setup.h" + +#include "silc-servers.h" +#include "silc-channels.h" +#include "silc-queries.h" +#include "silc-nicklist.h" +#include "window-item-def.h" + +#include "fe-common/core/printtext.h" +#include "fe-common/silc/module-formats.h" + +SILC_CHANNEL_REC *silc_channel_create(SILC_SERVER_REC *server, + const char *name, int automatic) +{ + SILC_CHANNEL_REC *rec; + + g_return_val_if_fail(server == NULL || IS_SILC_SERVER(server), NULL); + g_return_val_if_fail(name != NULL, NULL); + + rec = g_new0(SILC_CHANNEL_REC, 1); + rec->chat_type = SILC_PROTOCOL; + rec->name = g_strdup(name); + rec->server = server; + + channel_init((CHANNEL_REC *) rec, automatic); + return rec; +} + +static void sig_channel_destroyed(SILC_CHANNEL_REC *channel) +{ + if (!IS_SILC_CHANNEL(channel)) + return; + + if (channel->server != NULL && !channel->left && !channel->kicked) { + /* destroying channel record without actually + having left the channel yet */ + silc_command_exec(channel->server, "PART", channel->name); + } +} + +static void silc_channels_join(SILC_SERVER_REC *server, + const char *channels, int automatic) +{ + char **list, **tmp, *channel; + SILC_CHANNEL_REC *chanrec; + + list = g_strsplit(channels, ",", -1); + for (tmp = list; *tmp != NULL; tmp++) { + channel = **tmp == '#' ? g_strdup(*tmp) : + g_strconcat("#", *tmp, NULL); + + chanrec = silc_channel_find(server, channel); + if (chanrec) { + g_free(channel); + continue; + } + + silc_command_exec(server, "JOIN", channel); + g_free(channel); + } + + g_strfreev(list); +} + +static void sig_connected(SILC_SERVER_REC *server) +{ + if (IS_SILC_SERVER(server)) + server->channels_join = (void *) silc_channels_join; +} + +/* "server quit" signal from the core to indicate that QUIT command + was called. */ + +static void sig_server_quit(SILC_SERVER_REC *server, const char *msg) +{ + if (IS_SILC_SERVER(server) && server->conn && server->conn->sock) + silc_command_exec(server, "QUIT", msg); +} + +/* + * "event join". Joined to a channel. + */ + +SILC_CHANNEL_REC *silc_channel_find_entry(SILC_SERVER_REC *server, + SilcChannelEntry entry) +{ + GSList *tmp; + + g_return_val_if_fail(IS_SILC_SERVER(server), NULL); + + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + SILC_CHANNEL_REC *rec = tmp->data; + + if (rec->entry == entry) + return rec; + } + + return NULL; +} + +static void event_join(SILC_SERVER_REC *server, va_list va) +{ + SILC_CHANNEL_REC *chanrec; + SILC_NICK_REC *nickrec; + SilcClientEntry client; + SilcChannelEntry channel; + char userhost[256]; + + client = va_arg(va, SilcClientEntry); + channel = va_arg(va, SilcChannelEntry); + + if (client == server->conn->local_entry) { + /* You joined to channel */ + chanrec = silc_channel_find(server, channel->channel_name); + if (chanrec != NULL && !chanrec->joined) + chanrec->entry = channel; + } else { + chanrec = silc_channel_find_entry(server, channel); + if (chanrec != NULL) { + SilcChannelUser user; + + silc_list_start(chanrec->entry->clients); + while ((user = silc_list_get(chanrec->entry->clients)) != NULL) + if (user->client == client) { + nickrec = silc_nicklist_insert(chanrec, user, TRUE); + break; + } + } + } + + memset(userhost, 0, sizeof(userhost)); + if (client->username) + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + client->username, client->hostname); + signal_emit("message join", 4, server, channel->channel_name, + client->nickname, + client->username == NULL ? "" : userhost); +} + +/* + * "event leave". Left a channel. + */ + +static void event_leave(SILC_SERVER_REC *server, va_list va) +{ + SILC_CHANNEL_REC *chanrec; + SILC_NICK_REC *nickrec; + SilcClientEntry client; + SilcChannelEntry channel; + char userhost[256]; + + client = va_arg(va, SilcClientEntry); + channel = va_arg(va, SilcChannelEntry); + + memset(userhost, 0, sizeof(userhost)); + if (client->username) + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + client->username, client->hostname); + signal_emit("message part", 5, server, channel->channel_name, + client->nickname, client->username ? userhost : "", + client->nickname); + + chanrec = silc_channel_find_entry(server, channel); + if (chanrec != NULL) { + nickrec = silc_nicklist_find(chanrec, client); + if (nickrec != NULL) + nicklist_remove(CHANNEL(chanrec), NICK(nickrec)); + } +} + +/* + * "event signoff". Left the network. + */ + +static void event_signoff(SILC_SERVER_REC *server, va_list va) +{ + SilcClientEntry client; + GSList *nicks, *tmp; + char *message; + char userhost[256]; + + client = va_arg(va, SilcClientEntry); + message = va_arg(va, char *); + + silc_server_free_ftp(server, client); + + memset(userhost, 0, sizeof(userhost)); + if (client->username) + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + client->username, client->hostname); + signal_emit("message quit", 4, server, client->nickname, + client->username ? userhost : "", + message ? message : ""); + + nicks = nicklist_get_same_unique(SERVER(server), client); + for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) { + CHANNEL_REC *channel = tmp->data; + NICK_REC *nickrec = tmp->next->data; + + nicklist_remove(channel, nickrec); + } +} + +/* + * "event topic". Changed topic. + */ + +static void event_topic(SILC_SERVER_REC *server, va_list va) +{ + SILC_CHANNEL_REC *chanrec; + SilcClientEntry client; + SilcChannelEntry channel; + char *topic; + char userhost[256]; + + client = va_arg(va, SilcClientEntry); + topic = va_arg(va, char *); + channel = va_arg(va, SilcChannelEntry); + + silc_server_free_ftp(server, client); + + chanrec = silc_channel_find_entry(server, channel); + if (chanrec != NULL) { + g_free_not_null(chanrec->topic); + chanrec->topic = *topic == '\0' ? NULL : g_strdup(topic); + signal_emit("channel topic changed", 1, chanrec); + } + + memset(userhost, 0, sizeof(userhost)); + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + client->username, client->hostname); + signal_emit("message topic", 5, server, channel->channel_name, + topic, client->nickname, userhost); +} + +/* + * "event invite". Invited or modified invite list. + */ + +static void event_invite(SILC_SERVER_REC *server, va_list va) +{ + SilcClientEntry client; + SilcChannelEntry channel; + char *channel_name; + char userhost[256]; + + channel = va_arg(va, SilcChannelEntry); + channel_name = va_arg(va, char *); + client = va_arg(va, SilcClientEntry); + + memset(userhost, 0, sizeof(userhost)); + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + client->username, client->hostname); + signal_emit("message invite", 4, server, channel ? channel->channel_name : + channel_name, client->nickname, userhost); +} + +/* + * "event nick". Changed nickname. + */ + +static void event_nick(SILC_SERVER_REC *server, va_list va) +{ + SilcClientEntry oldclient, newclient; + char userhost[256]; + + oldclient = va_arg(va, SilcClientEntry); + newclient = va_arg(va, SilcClientEntry); + + nicklist_rename_unique(SERVER(server), + oldclient, oldclient->nickname, + newclient, newclient->nickname); + + memset(userhost, 0, sizeof(userhost)); + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + newclient->username, newclient->hostname); + signal_emit("message nick", 4, server, newclient->nickname, + oldclient->nickname, userhost); +} + +/* + * "event cmode". Changed channel mode. + */ + +static void event_cmode(SILC_SERVER_REC *server, va_list va) +{ + SILC_CHANNEL_REC *chanrec; + void *entry; + SilcClientEntry client; + SilcServerEntry server_entry; + SilcChannelEntry channel; + char *mode; + uint32 modei; + SilcIdType idtype; + + idtype = va_arg(va, int); + entry = va_arg(va, void *); + modei = va_arg(va, uint32); + (void)va_arg(va, char *); + (void)va_arg(va, char *); + channel = va_arg(va, SilcChannelEntry); + + mode = silc_client_chmode(modei, + channel->channel_key->cipher->name, + silc_hmac_get_name(channel->hmac)); + + chanrec = silc_channel_find_entry(server, channel); + if (chanrec != NULL) { + g_free_not_null(chanrec->mode); + chanrec->mode = g_strdup(mode == NULL ? "" : mode); + signal_emit("channel mode changed", 1, chanrec); + } + + if (idtype == SILC_ID_CLIENT) { + client = (SilcClientEntry)entry; + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_MODES, SILCTXT_CHANNEL_CMODE, + channel->channel_name, mode ? mode : "removed all", + client->nickname); + } else { + server_entry = (SilcServerEntry)entry; + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_MODES, SILCTXT_CHANNEL_CMODE, + channel->channel_name, mode ? mode : "removed all", + server_entry->server_name); + } + + g_free(mode); +} + +/* + * "event cumode". Changed user's mode on channel. + */ + +static void event_cumode(SILC_SERVER_REC *server, va_list va) +{ + SILC_CHANNEL_REC *chanrec; + SilcClientEntry client, destclient; + SilcChannelEntry channel; + int mode; + char *modestr; + + client = va_arg(va, SilcClientEntry); + mode = va_arg(va, uint32); + destclient = va_arg(va, SilcClientEntry); + channel = va_arg(va, SilcChannelEntry); + + modestr = silc_client_chumode(mode); + chanrec = silc_channel_find_entry(server, channel); + if (chanrec != NULL) { + SILC_NICK_REC *nick; + + if (destclient == server->conn->local_entry) { + chanrec->chanop = + (mode & SILC_CHANNEL_UMODE_CHANOP) != 0; + } + + nick = silc_nicklist_find(chanrec, destclient); + if (nick != NULL) { + nick->op = (mode & SILC_CHANNEL_UMODE_CHANOP) != 0; + nick->founder = (mode & SILC_CHANNEL_UMODE_CHANFO) != 0; + signal_emit("nick mode changed", 2, chanrec, nick); + } + } + + printformat_module("fe-common/silc", server, channel->channel_name, + MSGLEVEL_MODES, SILCTXT_CHANNEL_CUMODE, + channel->channel_name, destclient->nickname, + modestr ? modestr : "removed all", + client->nickname); + + if (mode & SILC_CHANNEL_UMODE_CHANFO) + printformat_module("fe-common/silc", + server, channel->channel_name, MSGLEVEL_CRAP, + SILCTXT_CHANNEL_FOUNDER, + channel->channel_name, destclient->nickname); + + g_free(modestr); +} + +/* + * "event motd". Received MOTD. + */ + +static void event_motd(SILC_SERVER_REC *server, va_list va) +{ + char *text = va_arg(va, char *); + + if (!settings_get_bool("skip_motd")) + printtext_multiline(server, NULL, MSGLEVEL_CRAP, "%s", text); +} + +/* + * "event channel_change". Channel ID has changed. + */ + +static void event_channel_change(SILC_SERVER_REC *server, va_list va) +{ + /* Nothing interesting to do */ +} + +/* + * "event server_signoff". Server has quit the network. + */ + +static void event_server_signoff(SILC_SERVER_REC *server, va_list va) +{ + SilcClientEntry *clients; + uint32 clients_count; + int i; + char userhost[256]; + + (void)va_arg(va, void *); + clients = va_arg(va, SilcClientEntry *); + clients_count = va_arg(va, uint32); + + for (i = 0; i < clients_count; i++) { + memset(userhost, 0, sizeof(userhost)); + if (clients[i]->username) + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + clients[i]->username, clients[i]->hostname); + signal_emit("message quit", 4, server, clients[i]->nickname, + clients[i]->username ? userhost : "", + "server signoff"); + } +} + +/* + * "event kick". Someone was kicked from channel. + */ + +static void event_kick(SILC_SERVER_REC *server, va_list va) +{ + SilcClientConnection conn = server->conn; + SilcClientEntry client_entry; + SilcChannelEntry channel_entry; + char *tmp; + SILC_CHANNEL_REC *chanrec; + + client_entry = va_arg(va, SilcClientEntry); + tmp = va_arg(va, char *); + channel_entry = va_arg(va, SilcChannelEntry); + + chanrec = silc_channel_find_entry(server, channel_entry); + + if (client_entry == conn->local_entry) { + printformat_module("fe-common/silc", server, channel_entry->channel_name, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_KICKED_YOU, + channel_entry->channel_name, tmp ? tmp : ""); + if (chanrec) { + chanrec->kicked = TRUE; + channel_destroy((CHANNEL_REC *)chanrec); + } + } else { + printformat_module("fe-common/silc", server, channel_entry->channel_name, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_KICKED, + client_entry->nickname, + channel_entry->channel_name, tmp ? tmp : ""); + + if (chanrec) { + SILC_NICK_REC *nickrec = silc_nicklist_find(chanrec, client_entry); + if (nickrec != NULL) + nicklist_remove(CHANNEL(chanrec), NICK(nickrec)); + } + } +} + +/* + * "event kill". Someone was killed from the network. + */ + +static void event_kill(SILC_SERVER_REC *server, va_list va) +{ + SilcClientConnection conn = server->conn; + SilcClientEntry client_entry; + char *tmp; + + client_entry = va_arg(va, SilcClientEntry); + tmp = va_arg(va, char *); + + if (client_entry == conn->local_entry) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED_YOU, + tmp ? tmp : ""); + } else { + GSList *nicks, *tmpn; + nicks = nicklist_get_same_unique(SERVER(server), client_entry); + for (tmpn = nicks; tmpn != NULL; tmpn = tmpn->next->next) { + CHANNEL_REC *channel = tmpn->data; + NICK_REC *nickrec = tmpn->next->data; + nicklist_remove(channel, nickrec); + } + + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_CHANNEL_KILLED, + client_entry->nickname, + tmp ? tmp : ""); + } +} + +/* PART (LEAVE) command. */ + +static void command_part(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + SILC_CHANNEL_REC *chanrec; + char userhost[256]; + + if (!IS_SILC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!strcmp(data, "*") || *data == '\0') { + if (!IS_SILC_CHANNEL(item)) + cmd_return_error(CMDERR_NOT_JOINED); + data = item->name; + } + + chanrec = silc_channel_find(server, data); + if (chanrec == NULL) + cmd_return_error(CMDERR_CHAN_NOT_FOUND); + + memset(userhost, 0, sizeof(userhost)); + snprintf(userhost, sizeof(userhost) - 1, "%s@%s", + server->conn->local_entry->username, + server->conn->local_entry->hostname); + signal_emit("message part", 5, server, chanrec->name, + server->nick, userhost, ""); + + silc_command_exec(server, "LEAVE", chanrec->name); + signal_stop(); + + channel_destroy(CHANNEL(chanrec)); +} + +/* ME local command. */ + +static void command_me(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + SILC_CHANNEL_REC *chanrec; + char *tmpcmd = "ME", *tmp; + uint32 argc = 0; + unsigned char **argv; + uint32 *argv_lens, *argv_types; + int i; + + if (!IS_SILC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!IS_SILC_CHANNEL(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + /* Now parse all arguments */ + tmp = g_strconcat(tmpcmd, " ", data, NULL); + silc_parse_command_line(tmp, &argv, &argv_lens, + &argv_types, &argc, 2); + g_free(tmp); + + if (argc < 2) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = silc_channel_find(server, item->name); + if (chanrec == NULL) + cmd_return_error(CMDERR_CHAN_NOT_FOUND); + + /* Send the action message */ + silc_client_send_channel_message(silc_client, server->conn, + chanrec->entry, NULL, + SILC_MESSAGE_FLAG_ACTION, + argv[1], argv_lens[1], TRUE); + + printformat_module("fe-common/silc", server, chanrec->entry->channel_name, + MSGLEVEL_ACTIONS, SILCTXT_CHANNEL_OWNACTION, + server->conn->local_entry->nickname, argv[1]); + + for (i = 0; i < argc; i++) + silc_free(argv[i]); + silc_free(argv_lens); + silc_free(argv_types); +} + +/* ACTION local command. Same as ME but takes the channel as mandatory + argument. */ + +static void command_action(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + SILC_CHANNEL_REC *chanrec; + char *tmpcmd = "ME", *tmp; + uint32 argc = 0; + unsigned char **argv; + uint32 *argv_lens, *argv_types; + int i; + + if (!IS_SILC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!IS_SILC_CHANNEL(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + /* Now parse all arguments */ + tmp = g_strconcat(tmpcmd, " ", data, NULL); + silc_parse_command_line(tmp, &argv, &argv_lens, + &argv_types, &argc, 3); + g_free(tmp); + + if (argc < 3) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = silc_channel_find(server, argv[1]); + if (chanrec == NULL) + cmd_return_error(CMDERR_CHAN_NOT_FOUND); + + /* Send the action message */ + silc_client_send_channel_message(silc_client, server->conn, + chanrec->entry, NULL, + SILC_MESSAGE_FLAG_ACTION, + argv[2], argv_lens[2], TRUE); + + printformat_module("fe-common/silc", server, chanrec->entry->channel_name, + MSGLEVEL_ACTIONS, SILCTXT_CHANNEL_OWNACTION, + server->conn->local_entry->nickname, argv[2]); + + for (i = 0; i < argc; i++) + silc_free(argv[i]); + silc_free(argv_lens); + silc_free(argv_types); +} + +/* NOTICE local command. */ + +static void command_notice(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + SILC_CHANNEL_REC *chanrec; + char *tmpcmd = "ME", *tmp; + uint32 argc = 0; + unsigned char **argv; + uint32 *argv_lens, *argv_types; + int i; + + if (!IS_SILC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (!IS_SILC_CHANNEL(item)) + cmd_return_error(CMDERR_NOT_JOINED); + + /* Now parse all arguments */ + tmp = g_strconcat(tmpcmd, " ", data, NULL); + silc_parse_command_line(tmp, &argv, &argv_lens, + &argv_types, &argc, 2); + g_free(tmp); + + if (argc < 2) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + chanrec = silc_channel_find(server, item->name); + if (chanrec == NULL) + cmd_return_error(CMDERR_CHAN_NOT_FOUND); + + /* Send the action message */ + silc_client_send_channel_message(silc_client, server->conn, + chanrec->entry, NULL, + SILC_MESSAGE_FLAG_NOTICE, + argv[1], argv_lens[1], TRUE); + + printformat_module("fe-common/silc", server, chanrec->entry->channel_name, + MSGLEVEL_NOTICES, SILCTXT_CHANNEL_OWNNOTICE, + server->conn->local_entry->nickname, argv[1]); + + for (i = 0; i < argc; i++) + silc_free(argv[i]); + silc_free(argv_lens); + silc_free(argv_types); +} + +/* AWAY local command. Sends UMODE command that sets the SILC_UMODE_GONE + flag. */ + +static void command_away(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + bool set; + + if (!IS_SILC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + if (*data == '\0') { + /* Remove any possible away message */ + silc_client_set_away_message(silc_client, server->conn, NULL); + set = FALSE; + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_UNSET_AWAY); + } else { + /* Set the away message */ + silc_client_set_away_message(silc_client, server->conn, (char *)data); + set = TRUE; + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_SET_AWAY, data); + } + + signal_emit("away mode changed", 1, server); + + silc_command_exec(server, "UMODE", set ? "+g" : "-g"); +} + +typedef struct { + int type; /* 1 = msg, 2 = channel */ + bool responder; + SILC_SERVER_REC *server; +} *KeyInternal; + +/* Key agreement callback that is called after the key agreement protocol + has been performed. This is called also if error occured during the + key agreement protocol. The `key' is the allocated key material and + the caller is responsible of freeing it. The `key' is NULL if error + has occured. The application can freely use the `key' to whatever + purpose it needs. See lib/silcske/silcske.h for the definition of + the SilcSKEKeyMaterial structure. */ + +static void keyagr_completion(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry, + SilcKeyAgreementStatus status, + SilcSKEKeyMaterial *key, + void *context) +{ + KeyInternal i = (KeyInternal)context; + + switch(status) { + case SILC_KEY_AGREEMENT_OK: + printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_OK, client_entry->nickname); + + if (i->type == 1) { + /* Set the private key for this client */ + silc_client_del_private_message_key(client, conn, client_entry); + silc_client_add_private_message_key_ske(client, conn, client_entry, + NULL, key, i->responder); + printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_PRIVMSG, + client_entry->nickname); + silc_ske_free_key_material(key); + } + + break; + + case SILC_KEY_AGREEMENT_ERROR: + printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_ERROR, client_entry->nickname); + break; + + case SILC_KEY_AGREEMENT_FAILURE: + printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_FAILURE, client_entry->nickname); + break; + + case SILC_KEY_AGREEMENT_TIMEOUT: + printformat_module("fe-common/silc", i->server, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_TIMEOUT, client_entry->nickname); + break; + + default: + break; + } + + if (i) + silc_free(i); +} + +/* Local command KEY. This command is used to set and unset private + keys for channels, set and unset private keys for private messages + with remote clients and to send key agreement requests and + negotiate the key agreement protocol with remote client. The + key agreement is supported only to negotiate private message keys, + it currently cannot be used to negotiate private keys for channels, + as it is not convenient for that purpose. */ + +typedef struct { + SILC_SERVER_REC *server; + char *data; + char *nick; + WI_ITEM_REC *item; +} *KeyGetClients; + +/* Callback to be called after client information is resolved from the + server. */ + +static void silc_client_command_key_get_clients(SilcClient client, + SilcClientConnection conn, + SilcClientEntry *clients, + uint32 clients_count, + void *context) +{ + KeyGetClients internal = (KeyGetClients)context; + + if (!clients) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown nick: %s", + internal->nick); + silc_free(internal->data); + silc_free(internal->nick); + silc_free(internal); + return; + } + + signal_emit("command key", 3, internal->data, internal->server, + internal->item); + + silc_free(internal->data); + silc_free(internal->nick); + silc_free(internal); +} + +static void command_key(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + SilcClientConnection conn; + SilcClientEntry *entrys, client_entry = NULL; + uint32 entry_count; + SilcChannelEntry channel_entry = NULL; + char *nickname = NULL, *tmp; + int command = 0, port = 0, type = 0; + char *hostname = NULL; + KeyInternal internal = NULL; + uint32 argc = 0; + unsigned char **argv; + uint32 *argv_lens, *argv_types; + char *bindhost = NULL; + + if (!server || !IS_SILC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + conn = server->conn; + + /* Now parse all arguments */ + tmp = g_strconcat("KEY", " ", data, NULL); + silc_parse_command_line(tmp, &argv, &argv_lens, &argv_types, &argc, 7); + g_free(tmp); + + if (argc < 4) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + /* Get type */ + if (!strcasecmp(argv[1], "msg")) + type = 1; + if (!strcasecmp(argv[1], "channel")) + type = 2; + + if (type == 0) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + if (type == 1) { + if (argv[2][0] == '*') { + nickname = strdup("*"); + } else { + /* Parse the typed nickname. */ + if (!silc_parse_userfqdn(argv[2], &nickname, NULL)) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_BAD_NICK, argv[2]); + return; + } + + /* Find client entry */ + entrys = silc_client_get_clients_local(silc_client, conn, nickname, + argv[2], &entry_count); + if (!entrys) { + KeyGetClients inter = silc_calloc(1, sizeof(*inter)); + inter->server = server; + inter->data = strdup(data); + inter->nick = strdup(nickname); + inter->item = item; + silc_client_get_clients(silc_client, conn, nickname, argv[2], + silc_client_command_key_get_clients, inter); + goto out; + } + client_entry = entrys[0]; + silc_free(entrys); + } + } + + if (type == 2) { + /* Get channel entry */ + char *name; + + if (argv[2][0] == '*') { + if (!conn->current_channel) { + silc_free(nickname); + cmd_return_error(CMDERR_NOT_JOINED); + } + name = conn->current_channel->channel_name; + } else { + name = argv[2]; + } + + channel_entry = silc_client_get_channel(silc_client, conn, name); + if (!channel_entry) { + silc_free(nickname); + cmd_return_error(CMDERR_NOT_JOINED); + } + } + + /* Set command */ + if (!strcasecmp(argv[3], "set")) { + command = 1; + + if (argc >= 5) { + if (type == 1 && client_entry) { + /* Set private message key */ + + silc_client_del_private_message_key(silc_client, conn, client_entry); + + if (argc >= 6) + silc_client_add_private_message_key(silc_client, conn, client_entry, + argv[5], argv[4], + argv_lens[4], + (argv[4][0] == '*' ? + TRUE : FALSE), FALSE); + else + silc_client_add_private_message_key(silc_client, conn, client_entry, + NULL, argv[4], + argv_lens[4], + (argv[4][0] == '*' ? + TRUE : FALSE), FALSE); + + /* Send the key to the remote client so that it starts using it + too. */ + silc_client_send_private_message_key(silc_client, conn, + client_entry, TRUE); + } else if (type == 2) { + /* Set private channel key */ + char *cipher = NULL, *hmac = NULL; + + if (!(channel_entry->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CH_PRIVATE_KEY_NOMODE, + channel_entry->channel_name); + goto out; + } + + if (argc >= 6) + cipher = argv[5]; + if (argc >= 7) + hmac = argv[6]; + + if (!silc_client_add_channel_private_key(silc_client, conn, + channel_entry, + cipher, hmac, + argv[4], + argv_lens[4])) { + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CH_PRIVATE_KEY_ERROR, + channel_entry->channel_name); + goto out; + } + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CH_PRIVATE_KEY_ADD, + channel_entry->channel_name); + } + } + + goto out; + } + + /* Unset command */ + if (!strcasecmp(argv[3], "unset")) { + command = 2; + + if (type == 1 && client_entry) { + /* Unset private message key */ + silc_client_del_private_message_key(silc_client, conn, client_entry); + } else if (type == 2) { + /* Unset channel key(s) */ + SilcChannelPrivateKey *keys; + uint32 keys_count; + int number; + + if (argc == 4) + silc_client_del_channel_private_keys(silc_client, conn, + channel_entry); + + if (argc > 4) { + number = atoi(argv[4]); + keys = silc_client_list_channel_private_keys(silc_client, conn, + channel_entry, + &keys_count); + if (!keys) + goto out; + + if (!number || number > keys_count) { + silc_client_free_channel_private_keys(keys, keys_count); + goto out; + } + + silc_client_del_channel_private_key(silc_client, conn, channel_entry, + keys[number - 1]); + silc_client_free_channel_private_keys(keys, keys_count); + } + + goto out; + } + } + + /* List command */ + if (!strcasecmp(argv[3], "list")) { + command = 3; + + if (type == 1) { + SilcPrivateMessageKeys keys; + uint32 keys_count; + int k, i, len; + char buf[1024]; + + keys = silc_client_list_private_message_keys(silc_client, conn, + &keys_count); + if (!keys) + goto out; + + /* list the private message key(s) */ + if (nickname[0] == '*') { + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_PRIVATE_KEY_LIST); + for (k = 0; k < keys_count; k++) { + memset(buf, 0, sizeof(buf)); + strncat(buf, " ", 2); + len = strlen(keys[k].client_entry->nickname); + strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len); + if (len < 30) + for (i = 0; i < 30 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + len = strlen(keys[k].cipher); + strncat(buf, keys[k].cipher, len > 14 ? 14 : len); + if (len < 14) + for (i = 0; i < 14 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + if (keys[k].key) + strcat(buf, ""); + else + strcat(buf, "*generated*"); + + silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO, "%s", buf); + } + } else { + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_PRIVATE_KEY_LIST_NICK, + client_entry->nickname); + for (k = 0; k < keys_count; k++) { + if (keys[k].client_entry != client_entry) + continue; + + memset(buf, 0, sizeof(buf)); + strncat(buf, " ", 2); + len = strlen(keys[k].client_entry->nickname); + strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len); + if (len < 30) + for (i = 0; i < 30 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + len = strlen(keys[k].cipher); + strncat(buf, keys[k].cipher, len > 14 ? 14 : len); + if (len < 14) + for (i = 0; i < 14 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + if (keys[k].key) + strcat(buf, ""); + else + strcat(buf, "*generated*"); + + silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO, "%s", buf); + } + } + + silc_client_free_private_message_keys(keys, keys_count); + + } else if (type == 2) { + SilcChannelPrivateKey *keys; + uint32 keys_count; + int k, i, len; + char buf[1024]; + + keys = silc_client_list_channel_private_keys(silc_client, conn, + channel_entry, + &keys_count); + + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_CH_PRIVATE_KEY_LIST, + channel_entry->channel_name); + + if (!keys) + goto out; + + for (k = 0; k < keys_count; k++) { + memset(buf, 0, sizeof(buf)); + strncat(buf, " ", 2); + + len = strlen(keys[k]->cipher->cipher->name); + strncat(buf, keys[k]->cipher->cipher->name, len > 16 ? 16 : len); + if (len < 16) + for (i = 0; i < 16 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + len = strlen(silc_hmac_get_name(keys[k]->hmac)); + strncat(buf, silc_hmac_get_name(keys[k]->hmac), len > 16 ? 16 : len); + if (len < 16) + for (i = 0; i < 16 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + strcat(buf, ""); + + silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO, "%s", buf); + } + + silc_client_free_channel_private_keys(keys, keys_count); + } + + goto out; + } + + /* Send command is used to send key agreement */ + if (!strcasecmp(argv[3], "agreement")) { + command = 4; + + if (argc >= 5) + hostname = argv[4]; + if (argc >= 6) + port = atoi(argv[5]); + + internal = silc_calloc(1, sizeof(*internal)); + internal->type = type; + internal->server = server; + + if (!hostname) { + if (settings_get_bool("use_auto_addr")) { + + hostname = (char *)settings_get_str("auto_public_ip"); + +/* If the hostname isn't set, treat this case as if auto_public_ip wasn't + * set. + */ + if ((hostname) && (*hostname == '\0')) { + hostname = NULL; + } + else { + bindhost = (char *)settings_get_str("auto_bind_ip"); + +/* if the bind_ip isn't set, but the public_ip IS, then assume then + * public_ip is the same value as the bind_ip. + */ + if ((bindhost) && (*bindhost == '\0')) { + bindhost = hostname; + } + port = settings_get_int("auto_bind_port"); + } + } /* if use_auto_addr */ + } + } + + /* Start command is used to start key agreement (after receiving the + key_agreement client operation). */ + if (!strcasecmp(argv[3], "negotiate")) { + command = 5; + + if (argc >= 5) + hostname = argv[4]; + if (argc >= 6) + port = atoi(argv[5]); + + internal = silc_calloc(1, sizeof(*internal)); + internal->type = type; + internal->server = server; + } + + if (command == 0) { + silc_say(silc_client, conn, SILC_CLIENT_MESSAGE_INFO, + "Usage: /KEY msg|channel " + "set|unset|agreement|negotiate []"); + goto out; + } + + if (command == 4 && client_entry) { + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT, argv[2]); + internal->responder = TRUE; + silc_client_send_key_agreement(silc_client, conn, client_entry, hostname, + bindhost, port, 120, keyagr_completion, + internal); + if (!hostname) + silc_free(internal); + goto out; + } + + if (command == 5 && client_entry && hostname) { + printformat_module("fe-common/silc", server, NULL, MSGLEVEL_CRAP, + SILCTXT_KEY_AGREEMENT_NEGOTIATE, argv[2]); + internal->responder = FALSE; + silc_client_perform_key_agreement(silc_client, conn, client_entry, + hostname, port, keyagr_completion, + internal); + goto out; + } + + out: + silc_free(nickname); +} + +/* Lists locally saved client and server public keys. */ + +static void command_listkeys(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + +} + +void silc_channels_init(void) +{ + signal_add("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_add("server connected", (SIGNAL_FUNC) sig_connected); + signal_add("server quit", (SIGNAL_FUNC) sig_server_quit); + + signal_add("silc event join", (SIGNAL_FUNC) event_join); + signal_add("silc event leave", (SIGNAL_FUNC) event_leave); + signal_add("silc event signoff", (SIGNAL_FUNC) event_signoff); + signal_add("silc event topic", (SIGNAL_FUNC) event_topic); + signal_add("silc event invite", (SIGNAL_FUNC) event_invite); + signal_add("silc event nick", (SIGNAL_FUNC) event_nick); + signal_add("silc event cmode", (SIGNAL_FUNC) event_cmode); + signal_add("silc event cumode", (SIGNAL_FUNC) event_cumode); + signal_add("silc event motd", (SIGNAL_FUNC) event_motd); + signal_add("silc event channel_change", (SIGNAL_FUNC) event_channel_change); + signal_add("silc event server_signoff", (SIGNAL_FUNC) event_server_signoff); + signal_add("silc event kick", (SIGNAL_FUNC) event_kick); + signal_add("silc event kill", (SIGNAL_FUNC) event_kill); + + command_bind("part", MODULE_NAME, (SIGNAL_FUNC) command_part); + command_bind("me", MODULE_NAME, (SIGNAL_FUNC) command_me); + command_bind("action", MODULE_NAME, (SIGNAL_FUNC) command_action); + command_bind("notice", MODULE_NAME, (SIGNAL_FUNC) command_notice); + command_bind("away", MODULE_NAME, (SIGNAL_FUNC) command_away); + command_bind("key", MODULE_NAME, (SIGNAL_FUNC) command_key); + command_bind("listkeys", MODULE_NAME, (SIGNAL_FUNC) command_listkeys); + + silc_nicklist_init(); +} + +void silc_channels_deinit(void) +{ + signal_remove("channel destroyed", (SIGNAL_FUNC) sig_channel_destroyed); + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit); + + signal_remove("silc event join", (SIGNAL_FUNC) event_join); + signal_remove("silc event leave", (SIGNAL_FUNC) event_leave); + signal_remove("silc event signoff", (SIGNAL_FUNC) event_signoff); + signal_remove("silc event topic", (SIGNAL_FUNC) event_topic); + signal_remove("silc event invite", (SIGNAL_FUNC) event_invite); + signal_remove("silc event nick", (SIGNAL_FUNC) event_nick); + signal_remove("silc event cmode", (SIGNAL_FUNC) event_cmode); + signal_remove("silc event cumode", (SIGNAL_FUNC) event_cumode); + signal_remove("silc event motd", (SIGNAL_FUNC) event_motd); + signal_remove("silc event channel_change", + (SIGNAL_FUNC) event_channel_change); + signal_remove("silc event server_signoff", + (SIGNAL_FUNC) event_server_signoff); + signal_remove("silc event kick", (SIGNAL_FUNC) event_kick); + signal_remove("silc event kill", (SIGNAL_FUNC) event_kill); + + command_unbind("part", (SIGNAL_FUNC) command_part); + command_unbind("me", (SIGNAL_FUNC) command_me); + command_unbind("action", (SIGNAL_FUNC) command_action); + command_unbind("notice", (SIGNAL_FUNC) command_notice); + command_unbind("away", (SIGNAL_FUNC) command_away); + command_unbind("key", (SIGNAL_FUNC) command_key); + command_unbind("listkeys", (SIGNAL_FUNC) command_listkeys); + + silc_nicklist_deinit(); +} diff --git a/apps/irssi/src/silc/core/silc-channels.h b/apps/irssi/src/silc/core/silc-channels.h new file mode 100644 index 00000000..8a286b73 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-channels.h @@ -0,0 +1,34 @@ +#ifndef __SILC_CHANNELS_H +#define __SILC_CHANNELS_H + +#include "chat-protocols.h" +#include "channels.h" +#include "silc-servers.h" + +/* Returns SILC_CHANNEL_REC if it's SILC channel, NULL if it isn't. */ +#define SILC_CHANNEL(channel) \ + PROTO_CHECK_CAST(CHANNEL(channel), SILC_CHANNEL_REC, chat_type, "SILC") +#define IS_SILC_CHANNEL(channel) \ + (SILC_CHANNEL(channel) ? TRUE : FALSE) +#define silc_channel_find(server, name) \ + SILC_CHANNEL(channel_find(SERVER(server), name)) + +#define STRUCT_SERVER_REC SILC_SERVER_REC +typedef struct { +#include "channel-rec.h" + GSList *banlist; /* list of bans */ + GSList *ebanlist; /* list of ban exceptions */ + GSList *invitelist; /* invite list */ + SilcChannelEntry entry; +} SILC_CHANNEL_REC; + +void silc_channels_init(void); +void silc_channels_deinit(void); + +/* Create new SILC channel record */ +SILC_CHANNEL_REC *silc_channel_create(SILC_SERVER_REC *server, + const char *name, int automatic); +SILC_CHANNEL_REC *silc_channel_find_entry(SILC_SERVER_REC *server, + SilcChannelEntry entry); + +#endif diff --git a/apps/irssi/src/silc/core/silc-core.c b/apps/irssi/src/silc/core/silc-core.c new file mode 100644 index 00000000..767cb0b1 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-core.c @@ -0,0 +1,389 @@ +/* + + silc-core.c + + Author: Pekka Riikonen + + Copyright (C) 2001 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. + +*/ + +#include "module.h" +#include "chat-protocols.h" +#include "args.h" + +#include "chatnets.h" +#include "servers-setup.h" +#include "channels-setup.h" +#include "silc-servers.h" +#include "silc-channels.h" +#include "silc-queries.h" +#include "silc-nicklist.h" +#include "version_internal.h" +#include "version.h" + +#include "signals.h" +#include "levels.h" +#include "settings.h" +#include "fe-common/core/printtext.h" +#include "fe-common/core/fe-channels.h" +#include "fe-common/core/keyboard.h" +#include "fe-common/silc/module-formats.h" + +/* Command line option variables */ +static bool opt_create_keypair = FALSE; +static bool opt_debug = FALSE; +static bool opt_list_ciphers = FALSE; +static bool opt_list_hash = FALSE; +static bool opt_list_hmac = FALSE; +static bool opt_list_pkcs = FALSE; +static bool opt_version = FALSE; +static char *opt_pkcs = NULL; +static char *opt_keyfile = NULL; +static int opt_bits = 0; + +static int idletag; + +SilcClient silc_client = NULL; +SilcClientConfig silc_config = NULL; +extern SilcClientOperations ops; +extern int silc_debug; +#ifdef SILC_SIM +/* SIM (SILC Module) table */ +SilcSimContext **sims = NULL; +uint32 sims_count = 0; +#endif + +static int my_silc_scheduler(void) +{ + silc_schedule_one(silc_client->schedule, 0); + return 1; +} + +static CHATNET_REC *create_chatnet(void) +{ + return g_malloc0(sizeof(CHATNET_REC)); +} + +static SERVER_SETUP_REC *create_server_setup(void) +{ + return g_malloc0(sizeof(SERVER_SETUP_REC)); +} + +static CHANNEL_SETUP_REC *create_channel_setup(void) +{ + return g_malloc0(sizeof(CHANNEL_SETUP_REC)); +} + +static SERVER_CONNECT_REC *create_server_connect(void) +{ + return g_malloc0(sizeof(SILC_SERVER_CONNECT_REC)); +} + +/* Checks user information and saves them to the config file it they + do not exist there already. */ + +static void silc_init_userinfo(void) +{ + const char *set, *nick, *user_name; + char *str; + + /* check if nick/username/realname wasn't read from setup.. */ + set = settings_get_str("real_name"); + if (set == NULL || *set == '\0') { + str = g_getenv("SILCNAME"); + if (!str) + str = g_getenv("IRCNAME"); + settings_set_str("real_name", + str != NULL ? str : g_get_real_name()); + } + + /* username */ + user_name = settings_get_str("user_name"); + if (user_name == NULL || *user_name == '\0') { + str = g_getenv("SILCUSER"); + if (!str) + str = g_getenv("IRCUSER"); + settings_set_str("user_name", + str != NULL ? str : g_get_user_name()); + + user_name = settings_get_str("user_name"); + } + + /* nick */ + nick = settings_get_str("nick"); + if (nick == NULL || *nick == '\0') { + str = g_getenv("SILCNICK"); + if (!str) + str = g_getenv("IRCNICK"); + settings_set_str("nick", str != NULL ? str : user_name); + + nick = settings_get_str("nick"); + } + + /* alternate nick */ + set = settings_get_str("alternate_nick"); + if (set == NULL || *set == '\0') { + if (strlen(nick) < 9) + str = g_strconcat(nick, "_", NULL); + else { + str = g_strdup(nick); + str[strlen(str)-1] = '_'; + } + settings_set_str("alternate_nick", str); + g_free(str); + } + + /* host name */ + set = settings_get_str("hostname"); + if (set == NULL || *set == '\0') { + str = g_getenv("SILCHOST"); + if (!str) + str = g_getenv("IRCHOST"); + if (str != NULL) + settings_set_str("hostname", str); + } +} + +/* Log callbacks */ + +static void silc_log_info(char *message) +{ + fprintf(stderr, "%s\n", message); +} + +static void silc_log_warning(char *message) +{ + fprintf(stderr, "%s\n", message); +} + +static void silc_log_error(char *message) +{ + fprintf(stderr, "%s\n", message); +} + +/* Init SILC. Called from src/fe-text/silc.c */ + +void silc_core_init(void) +{ + static struct poptOption options[] = { + { "create-key-pair", 'C', POPT_ARG_NONE, &opt_create_keypair, 0, + "Create new public key pair", NULL }, + { "pkcs", 0, POPT_ARG_STRING, &opt_pkcs, 0, + "Set the PKCS of the public key pair", "PKCS" }, + { "bits", 0, POPT_ARG_INT, &opt_bits, 0, + "Set the length of the public key pair", "VALUE" }, + { "show-key", 'S', POPT_ARG_STRING, &opt_keyfile, 0, + "Show the contents of the public key", "FILE" }, + { "list-ciphers", 'C', POPT_ARG_NONE, &opt_list_ciphers, 0, + "List supported ciphers", NULL }, + { "list-hash-funcs", 'H', POPT_ARG_NONE, &opt_list_hash, 0, + "List supported hash functions", NULL }, + { "list-hmacs", 'H', POPT_ARG_NONE, &opt_list_hmac, 0, + "List supported HMACs", NULL }, + { "list-pkcs", 'P', POPT_ARG_NONE, &opt_list_pkcs, 0, + "List supported PKCSs", NULL }, + { "debug", 'd', POPT_ARG_NONE, &opt_debug, 0, + "Enable debugging", NULL }, + { "version", 'V', POPT_ARG_NONE, &opt_version, 0, + "Show version", NULL }, + { NULL, '\0', 0, NULL } + }; + + args_register(options); +} + +static void silc_nickname_format_parse(const char *nickname, + char **ret_nickname) +{ + silc_parse_userfqdn(nickname, ret_nickname, NULL); +} + +/* Finalize init. Called from src/fe-text/silc.c */ + +void silc_core_init_finish(void) +{ + CHAT_PROTOCOL_REC *rec; + SilcClientParams params; + + if (opt_create_keypair == TRUE) { + /* Create new key pair and exit */ + silc_cipher_register_default(); + silc_pkcs_register_default(); + silc_hash_register_default(); + silc_hmac_register_default(); + silc_client_create_key_pair(opt_pkcs, opt_bits, + NULL, NULL, NULL, NULL, NULL); + exit(0); + } + + if (opt_keyfile) { + /* Dump the key */ + silc_cipher_register_default(); + silc_pkcs_register_default(); + silc_hash_register_default(); + silc_hmac_register_default(); + silc_client_show_key(opt_keyfile); + exit(0); + } + + if (opt_list_ciphers) { + silc_cipher_register_default(); + silc_client_list_ciphers(); + exit(0); + } + + if (opt_list_hash) { + silc_hash_register_default(); + silc_client_list_hash_funcs(); + exit(0); + } + + if (opt_list_hmac) { + silc_hmac_register_default(); + silc_client_list_hmacs(); + exit(0); + } + + if (opt_list_pkcs) { + silc_pkcs_register_default(); + silc_client_list_pkcs(); + exit(0); + } + + if (opt_version) { + printf("SILC Secure Internet Live Conferencing, version %s " + "(base: SILC Toolkit %s)\n", silc_dist_version, silc_version); + printf("(c) 1997 - 2001 Pekka Riikonen \n"); + exit(0); + } + + silc_debug = opt_debug; + silc_log_set_callbacks(silc_log_info, silc_log_warning, + silc_log_error, NULL); + + /* Do some irssi initializing */ + settings_add_bool("server", "skip_motd", FALSE); + settings_add_str("server", "alternate_nick", NULL); + + /* Initialize the auto_addr variables Is "server" the best choice for + * this? No existing category seems to apply. + */ + + settings_add_bool("server", "use_auto_addr", FALSE); + settings_add_str("server", "auto_bind_ip", ""); + settings_add_str("server", "auto_public_ip", ""); + settings_add_int("server", "auto_bind_port", 0); + + silc_init_userinfo(); + + /* Initialize client parameters */ + memset(¶ms, 0, sizeof(params)); + strcat(params.nickname_format, "%n@%h%a"); + params.nickname_parse = silc_nickname_format_parse; + + /* Allocate SILC client */ + silc_client = silc_client_alloc(&ops, ¶ms, NULL, silc_version_string); + + /* Load local config file */ + silc_config = silc_client_config_alloc(SILC_CLIENT_HOME_CONFIG_FILE); + + /* Get user information */ + silc_client->username = g_strdup(settings_get_str("user_name")); + silc_client->hostname = silc_net_localhost(); + silc_client->realname = g_strdup(settings_get_str("real_name")); + + /* Register all configured ciphers, PKCS and hash functions. */ + if (silc_config) { + silc_config->client = silc_client; + if (!silc_client_config_register_ciphers(silc_config)) + silc_cipher_register_default(); + if (!silc_client_config_register_pkcs(silc_config)) + silc_pkcs_register_default(); + if (!silc_client_config_register_hashfuncs(silc_config)) + silc_hash_register_default(); + if (!silc_client_config_register_hmacs(silc_config)) + silc_hmac_register_default(); + } else { + /* Register default ciphers, pkcs, hash funtions and hmacs. */ + silc_cipher_register_default(); + silc_pkcs_register_default(); + silc_hash_register_default(); + silc_hmac_register_default(); + } + + /* Check ~/.silc directory and public and private keys */ + if (silc_client_check_silc_dir() == FALSE) { + idletag = -1; + return; + } + + /* Load public and private key */ + if (silc_client_load_keys(silc_client) == FALSE) { + idletag = -1; + return; + } + + /* Initialize the SILC client */ + if (!silc_client_init(silc_client)) { + idletag = -1; + return; + } + + /* Register SILC to the irssi */ + rec = g_new0(CHAT_PROTOCOL_REC, 1); + rec->name = "SILC"; + rec->fullname = "Secure Internet Live Conferencing"; + rec->chatnet = "silcnet"; + rec->create_chatnet = create_chatnet; + rec->create_server_setup = create_server_setup; + rec->create_channel_setup = create_channel_setup; + rec->create_server_connect = create_server_connect; + rec->server_connect = (SERVER_REC *(*) (SERVER_CONNECT_REC *)) + silc_server_connect; + rec->channel_create = (CHANNEL_REC *(*) (SERVER_REC *, const char *, int)) + silc_channel_create; + rec->query_create = (QUERY_REC *(*) (const char *, const char *, int)) + silc_query_create; + + chat_protocol_register(rec); + g_free(rec); + + silc_server_init(); + silc_channels_init(); + silc_queries_init(); + + idletag = g_timeout_add(5, (GSourceFunc) my_silc_scheduler, NULL); +} + +/* Deinit SILC. Called from src/fe-text/silc.c */ + +void silc_core_deinit(void) +{ + if (idletag != -1) { + signal_emit("chat protocol deinit", 1, + chat_protocol_find("SILC")); + + silc_server_deinit(); + silc_channels_deinit(); + silc_queries_deinit(); + + chat_protocol_unregister("SILC"); + + g_source_remove(idletag); + } + + g_free(silc_client->username); + g_free(silc_client->realname); + silc_client_free(silc_client); +} diff --git a/apps/irssi/src/silc/core/silc-core.h b/apps/irssi/src/silc/core/silc-core.h new file mode 100644 index 00000000..15ac7132 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-core.h @@ -0,0 +1,40 @@ +#ifndef __SILC_CORE_H +#define __SILC_CORE_H + +#include "clientconfig.h" +#include "clientutil.h" + +/* Default client configuration file. This can be overridden at the + compilation time. Otherwise, use default. This can be overridden on + command line as well. */ +#ifndef SILC_CLIENT_CONFIG_FILE +#define SILC_CLIENT_CONFIG_FILE "/etc/silc/silc.conf" +#endif + +/* Default user configuration file. This file is searched from user's + home directory. This may override global configuration settings. */ +#define SILC_CLIENT_HOME_CONFIG_FILE "~/.silc/silc.conf" + +/* Default public and private key file names */ +#define SILC_CLIENT_PUBLIC_KEY_NAME "public_key.pub" +#define SILC_CLIENT_PRIVATE_KEY_NAME "private_key.prv" + +/* Default key expiration time, one year. */ +#define SILC_CLIENT_KEY_EXPIRES 365 + +/* Default settings for creating key pair */ +#define SILC_CLIENT_DEF_PKCS "rsa" +#define SILC_CLIENT_DEF_PKCS_LEN 1024 + +extern SilcClient silc_client; +extern SilcClientConfig silc_config; + +#ifdef SILC_SIM +/* SIM (SILC Module) table */ +extern SilcSimContext **sims; +extern uint32 sims_count; +#endif + +#define IS_SILC_ITEM(rec) (IS_SILC_CHANNEL(rec) || IS_SILC_QUERY(rec)) + +#endif diff --git a/apps/irssi/src/silc/core/silc-nicklist.c b/apps/irssi/src/silc/core/silc-nicklist.c new file mode 100644 index 00000000..2a517db2 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-nicklist.c @@ -0,0 +1,140 @@ +/* + silc-nicklist.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" +#include "servers.h" + +#include "silc-channels.h" +#include "silc-nicklist.h" + +SILC_NICK_REC *silc_nicklist_insert(SILC_CHANNEL_REC *channel, + SilcChannelUser user, int send_massjoin) +{ + SILC_NICK_REC *rec; + + g_return_val_if_fail(IS_SILC_CHANNEL(channel), NULL); + g_return_val_if_fail(user != NULL, NULL); + + rec = g_new0(SILC_NICK_REC, 1); + rec->nick = g_strdup(user->client->nickname); + rec->host = g_strdup(user->client->username); + rec->silc_user = user; + rec->unique_id = user->client; + + if (user->mode & SILC_CHANNEL_UMODE_CHANOP) + rec->op = TRUE; + if (user->mode & SILC_CHANNEL_UMODE_CHANFO) + rec->founder = TRUE; + rec->send_massjoin = send_massjoin; + + nicklist_insert(CHANNEL(channel), (NICK_REC *) rec); + return rec; +} + +SILC_NICK_REC *silc_nicklist_find(SILC_CHANNEL_REC *channel, + SilcClientEntry client) +{ + if (!client || !client->nickname) + return NULL; + + return (SILC_NICK_REC *)nicklist_find_unique(CHANNEL(channel), + client->nickname, client); +} + +#define isnickchar(a) \ + (isalnum((int) (a)) || (a) == '`' || (a) == '-' || (a) == '_' || \ + (a) == '[' || (a) == ']' || (a) == '{' || (a) == '}' || \ + (a) == '|' || (a) == '\\' || (a) == '^') + +/* Remove all "extra" characters from `nick'. Like _nick_ -> nick */ +char *silc_nick_strip(const char *nick) +{ + char *stripped, *spos; + + g_return_val_if_fail(nick != NULL, NULL); + + spos = stripped = g_strdup(nick); + while (isnickchar(*nick)) { + if (isalnum((int) *nick)) + *spos++ = *nick; + nick++; + } + if ((unsigned char) *nick >= 128) + *spos++ = *nick; /* just add it so that nicks won't match.. */ + *spos = '\0'; + + return stripped; +} + +/* Check is `msg' is meant for `nick'. */ +int silc_nick_match(const char *nick, const char *msg) +{ + char *stripnick, *stripmsg; + int ret, len; + + g_return_val_if_fail(nick != NULL, FALSE); + g_return_val_if_fail(msg != NULL, FALSE); + + len = strlen(nick); + if (g_strncasecmp(msg, nick, len) == 0 && !isalnum((int) msg[len])) + return TRUE; + + stripnick = silc_nick_strip(nick); + stripmsg = silc_nick_strip(msg); + + len = strlen(stripnick); + ret = len > 0 && g_strncasecmp(stripmsg, stripnick, len) == 0 && + !isalnum((int) stripmsg[len]) && + (unsigned char) stripmsg[len] < 128; + + g_free(stripnick); + g_free(stripmsg); + + return ret; +} + +static const char *get_nick_flags(void) +{ + static char flags[3] = { '@', '+', '\0' }; + return flags; +} + +static void sig_connected(SILC_SERVER_REC *server) +{ + if (IS_SILC_SERVER(server)) + server->get_nick_flags = (void *) get_nick_flags; +} + +void silc_change_nick(SILC_SERVER_REC *server, const char *newnick) +{ + server_change_nick((SERVER_REC *)server, newnick); +} + +void silc_nicklist_init(void) +{ + signal_add("server connected", (SIGNAL_FUNC) sig_connected); +} + +void silc_nicklist_deinit(void) +{ + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); +} diff --git a/apps/irssi/src/silc/core/silc-nicklist.h b/apps/irssi/src/silc/core/silc-nicklist.h new file mode 100644 index 00000000..b1139478 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-nicklist.h @@ -0,0 +1,24 @@ +#ifndef __SILC_NICKLIST_H +#define __SILC_NICKLIST_H + +#include "nicklist.h" + +typedef struct { +#include "nick-rec.h" + SilcChannelUser silc_user; + unsigned int founder:1; +} SILC_NICK_REC; + +SILC_NICK_REC *silc_nicklist_insert(SILC_CHANNEL_REC *channel, + SilcChannelUser user, int send_massjoin); + +SILC_NICK_REC *silc_nicklist_find(SILC_CHANNEL_REC *channel, + SilcClientEntry client); + +/* Check if `msg' is meant for `nick'. */ +int silc_nick_match(const char *nick, const char *msg); +void silc_change_nick(SILC_SERVER_REC *server, const char *newnick); +void silc_nicklist_init(void); +void silc_nicklist_deinit(void); + +#endif diff --git a/apps/irssi/src/silc/core/silc-queries.c b/apps/irssi/src/silc/core/silc-queries.c new file mode 100644 index 00000000..6717cb36 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-queries.c @@ -0,0 +1,48 @@ +/* + silc-queries.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" +#include "misc.h" + +#include "silc-queries.h" + +QUERY_REC *silc_query_create(const char *server_tag, + const char *nick, int automatic) +{ + QUERY_REC *rec; + + g_return_val_if_fail(nick != NULL, NULL); + + rec = g_new0(QUERY_REC, 1); + rec->chat_type = SILC_PROTOCOL; + rec->name = g_strdup(nick); + rec->server_tag = g_strdup(server_tag); + query_init(rec, automatic); + return rec; +} + +void silc_queries_init(void) +{ +} + +void silc_queries_deinit(void) +{ +} diff --git a/apps/irssi/src/silc/core/silc-queries.h b/apps/irssi/src/silc/core/silc-queries.h new file mode 100644 index 00000000..9443b59c --- /dev/null +++ b/apps/irssi/src/silc/core/silc-queries.h @@ -0,0 +1,21 @@ +#ifndef __SILC_QUERIES_H +#define __SILC_QUERIES_H + +#include "chat-protocols.h" +#include "queries.h" +#include "silc-servers.h" + +/* Returns SILC_QUERY_REC if it's SILC query, NULL if it isn't. */ +#define SILC_QUERY(query) \ + PROTO_CHECK_CAST(QUERY(query), QUERY_REC, chat_type, "SILC") +#define IS_SILC_QUERY(query) \ + (SILC_QUERY(query) ? TRUE : FALSE) +#define silc_query_find(server, name) \ + query_find(SERVER(server), name) + +QUERY_REC *silc_query_create(const char *server_tag, + const char *nick, int automatic); +void silc_queries_init(void); +void silc_queries_deinit(void); + +#endif diff --git a/apps/irssi/src/silc/core/silc-servers-reconnect.c b/apps/irssi/src/silc/core/silc-servers-reconnect.c new file mode 100644 index 00000000..79a7b699 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-servers-reconnect.c @@ -0,0 +1,60 @@ +/* + silc-servers-reconnect.c : irssi + + Copyright (C) 2000 Timo Sirainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" +#include "signals.h" + +#include "silc-servers.h" + +static void sig_server_reconnect_save_status(SILC_SERVER_CONNECT_REC *conn, + SILC_SERVER_REC *server) +{ + if (!IS_SILC_SERVER_CONNECT(conn) || !IS_SILC_SERVER(server)) + return; + + g_free_not_null(conn->channels); + conn->channels = silc_server_get_channels(server); +} + +static void sig_server_connect_copy(SERVER_CONNECT_REC **dest, + SILC_SERVER_CONNECT_REC *src) +{ + SILC_SERVER_CONNECT_REC *rec; + + g_return_if_fail(dest != NULL); + if (!IS_SILC_SERVER_CONNECT(src)) + return; + + rec = g_new0(SILC_SERVER_CONNECT_REC, 1); + rec->chat_type = SILC_PROTOCOL; + *dest = (SERVER_CONNECT_REC *) rec; +} + +void silc_servers_reconnect_init(void) +{ + signal_add("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status); + signal_add("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy); +} + +void silc_servers_reconnect_deinit(void) +{ + signal_remove("server reconnect save status", (SIGNAL_FUNC) sig_server_reconnect_save_status); + signal_remove("server connect copy", (SIGNAL_FUNC) sig_server_connect_copy); +} diff --git a/apps/irssi/src/silc/core/silc-servers.c b/apps/irssi/src/silc/core/silc-servers.c new file mode 100644 index 00000000..f9f5854f --- /dev/null +++ b/apps/irssi/src/silc/core/silc-servers.c @@ -0,0 +1,914 @@ +/* + silc-server.c : irssi + + Copyright (C) 2000 - 2001 Timo Sirainen + Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "module.h" + +#include "net-nonblock.h" +#include "net-sendbuffer.h" +#include "signals.h" +#include "servers.h" +#include "commands.h" +#include "levels.h" +#include "modules.h" +#include "rawlog.h" +#include "misc.h" +#include "settings.h" + +#include "servers-setup.h" + +#include "silc-servers.h" +#include "silc-channels.h" +#include "silc-queries.h" +#include "silc-nicklist.h" +#include "window-item-def.h" + +#include "fe-common/core/printtext.h" +#include "fe-common/silc/module-formats.h" + +void silc_servers_reconnect_init(void); +void silc_servers_reconnect_deinit(void); + +static void silc_send_channel(SILC_SERVER_REC *server, + char *channel, char *msg) +{ + SILC_CHANNEL_REC *rec; + + rec = silc_channel_find(server, channel); + if (rec == NULL || rec->entry == NULL) + return; + + silc_client_send_channel_message(silc_client, server->conn, rec->entry, + NULL, 0, msg, strlen(msg), TRUE); +} + +typedef struct { + char *nick; + char *msg; + SILC_SERVER_REC *server; +} PRIVMSG_REC; + +/* Callback function that sends the private message if the client was + resolved from the server. */ + +static void silc_send_msg_clients(SilcClient client, + SilcClientConnection conn, + SilcClientEntry *clients, + uint32 clients_count, + void *context) +{ + PRIVMSG_REC *rec = context; + SILC_SERVER_REC *server = rec->server; + SilcClientEntry target; + char *nickname = NULL; + + if (!clients_count) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown nick: %s", rec->nick); + } else { + if (clients_count > 1) { + silc_parse_userfqdn(rec->nick, &nickname, NULL); + + /* Find the correct one. The rec->nick might be a formatted nick + so this will find the correct one. */ + clients = silc_client_get_clients_local(silc_client, server->conn, + nickname, rec->nick, + &clients_count); + if (!clients) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown nick: %s", + rec->nick); + silc_free(nickname); + goto out; + } + silc_free(nickname); + } + + target = clients[0]; + + /* Send the private message */ + silc_client_send_private_message(client, conn, target, 0, + rec->msg, strlen(rec->msg), + TRUE); + } + + out: + g_free(rec->nick); + g_free(rec->msg); + g_free(rec); +} + +static void silc_send_msg(SILC_SERVER_REC *server, char *nick, char *msg) +{ + PRIVMSG_REC *rec; + SilcClientEntry *clients; + uint32 clients_count; + char *nickname = NULL; + + if (!silc_parse_userfqdn(nick, &nickname, NULL)) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_BAD_NICK, nick); + return; + } + + /* Find client entry */ + clients = silc_client_get_clients_local(silc_client, server->conn, + nickname, nick, &clients_count); + if (!clients) { + rec = g_new0(PRIVMSG_REC, 1); + rec->nick = g_strdup(nick); + rec->msg = g_strdup(msg); + rec->server = server; + + /* Could not find client with that nick, resolve it from server. */ + silc_client_get_clients(silc_client, server->conn, + nickname, NULL, silc_send_msg_clients, rec); + silc_free(nickname); + return; + } + + /* Send the private message directly */ + silc_free(nickname); + silc_client_send_private_message(silc_client, server->conn, + clients[0], 0, msg, strlen(msg), TRUE); +} + +static int isnickflag_func(char flag) +{ + return flag == '@' || flag == '+'; +} + +static int ischannel_func(const char *data) +{ + return *data == '#'; +} + +const char *get_nick_flags(void) +{ + return "@\0\0"; +} + +static void send_message(SILC_SERVER_REC *server, char *target, char *msg) +{ + g_return_if_fail(server != NULL); + g_return_if_fail(target != NULL); + g_return_if_fail(msg != NULL); + + if (*target == '#') + silc_send_channel(server, target, msg); + else + silc_send_msg(server, target, msg); +} + +static void sig_connected(SILC_SERVER_REC *server) +{ + SilcClientConnection conn; + int fd; + + if (!IS_SILC_SERVER(server)) + return; + + conn = silc_client_add_connection(silc_client, + server->connrec->address, + server->connrec->port, + server); + server->conn = conn; + + fd = g_io_channel_unix_get_fd(net_sendbuffer_handle(server->handle)); + if (!silc_client_start_key_exchange(silc_client, conn, fd)) { + /* some internal error occured */ + server_disconnect(SERVER(server)); + signal_stop(); + return; + } + + server->isnickflag = isnickflag_func; + server->ischannel = ischannel_func; + server->get_nick_flags = get_nick_flags; + server->send_message = (void *) send_message; +} + +static void sig_disconnected(SILC_SERVER_REC *server) +{ + if (!IS_SILC_SERVER(server)) + return; + + if (server->conn && server->conn->sock != NULL) { + silc_client_close_connection(silc_client, NULL, server->conn); + + /* SILC closes the handle */ + g_io_channel_unref(net_sendbuffer_handle(server->handle)); + net_sendbuffer_destroy(server->handle, FALSE); + server->handle = NULL; + } +} + +SILC_SERVER_REC *silc_server_connect(SILC_SERVER_CONNECT_REC *conn) +{ + SILC_SERVER_REC *server; + + g_return_val_if_fail(IS_SILC_SERVER_CONNECT(conn), NULL); + if (conn->address == NULL || *conn->address == '\0') + return NULL; + if (conn->nick == NULL || *conn->nick == '\0') { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, + "Cannot connect: nickname is not set"); + return NULL; + } + + server = g_new0(SILC_SERVER_REC, 1); + server->chat_type = SILC_PROTOCOL; + server->connrec = conn; + if (server->connrec->port <= 0) + server->connrec->port = 706; + + if (!server_start_connect((SERVER_REC *) server)) { + server_connect_free(SERVER_CONNECT(conn)); + g_free(server); + return NULL; + } + + server->ftp_sessions = silc_dlist_init(); + + return server; +} + +/* Return a string of all channels in server in server->channels_join() + format */ + +char *silc_server_get_channels(SILC_SERVER_REC *server) +{ + GSList *tmp; + GString *chans; + char *ret; + + g_return_val_if_fail(server != NULL, FALSE); + + chans = g_string_new(NULL); + for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { + CHANNEL_REC *channel = tmp->data; + + g_string_sprintfa(chans, "%s,", channel->name); + } + + if (chans->len > 0) + g_string_truncate(chans, chans->len-1); + + ret = chans->str; + g_string_free(chans, FALSE); + + return ret; +} + +/* Syntaxes of all SILC commands for HELP files (the help file generation + will snoop these from here). */ + +/* SYNTAX: BAN [+|-[[@[![@hostname>]]]]] */ +/* SYNTAX: CMODE +|- [{ }] */ +/* SYNTAX: CUMODE +|- [@] [-pubkey|] */ +/* SYNTAX: GETKEY */ +/* SYNTAX: INVITE [[@hostname>] */ +/* SYNTAX: INVITE [+|-[[@[![@hostname>]]]]] */ +/* SYNTAX: KEY MSG set|unset|list|agreement|negotiate [] */ +/* SYNTAX: KEY CHANNEL set|unset|list|agreement|negotiate [] */ +/* SYNTAX: KICK [@] [] */ +/* SYNTAX: KILL [@] [] */ +/* SYNTAX: OPER [-pubkey] */ +/* SYNTAX: SILCOPER [-pubkey] */ +/* SYNTAX: TOPIC [] */ +/* SYNTAX: UMODE +|- */ +/* SYNTAX: WHOIS [@] [] */ +/* SYNTAX: WHOWAS [@] [] */ +/* SYNTAX: CLOSE [] */ +/* SYNTAX: SHUTDOWN */ +/* SYNTAX: MOTD [] */ +/* SYNTAX: LIST [] */ +/* SYNTAX: ME */ +/* SYNTAX: ACTION */ +/* SYNTAX: AWAY [] */ +/* SYNTAX: INFO [] */ +/* SYNTAX: NICK */ +/* SYNTAX: NOTICE */ +/* SYNTAX: PART [] */ +/* SYNTAX: PING */ +/* SYNTAX: SCONNECT [] */ +/* SYNTAX: USERS */ +/* SYNTAX: FILE SEND [ []] */ +/* SYNTAX: FILE RECEIVE [] */ +/* SYNTAX: FILE CLOSE [] */ +/* SYNTAX: FILE */ + +void silc_command_exec(SILC_SERVER_REC *server, + const char *command, const char *args) +{ + uint32 argc = 0; + unsigned char **argv; + uint32 *argv_lens, *argv_types; + char *data, *tmpcmd; + SilcClientCommand *cmd; + SilcClientCommandContext ctx; + + g_return_if_fail(server != NULL); + + tmpcmd = g_strdup(command); + g_strup(tmpcmd); + cmd = silc_client_command_find(tmpcmd); + g_free(tmpcmd); + if (cmd == NULL) + return; + + /* Now parse all arguments */ + data = g_strconcat(command, " ", args, NULL); + silc_parse_command_line(data, &argv, &argv_lens, + &argv_types, &argc, cmd->max_args); + g_free(data); + + /* Allocate command context. This and its internals must be free'd + by the command routine receiving it. */ + ctx = silc_client_command_alloc(); + ctx->client = silc_client; + ctx->conn = server->conn; + ctx->command = cmd; + ctx->argc = argc; + ctx->argv = argv; + ctx->argv_lens = argv_lens; + ctx->argv_types = argv_types; + + /* Execute command */ + (*cmd->cb)(ctx, NULL); +} + +/* Generic command function to call any SILC command directly. */ + +static void command_self(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + if (!IS_SILC_SERVER(server) || !server->connected) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Not connected to server"); + return; + } + + if (IS_SILC_CHANNEL(item)) { + SILC_CHANNEL_REC *chanrec; + chanrec = silc_channel_find(server, item->name); + if (chanrec) + server->conn->current_channel = chanrec->entry; + } + + silc_command_exec(server, current_command, data); + signal_stop(); +} + +/* SCONNECT command. Calls actually SILC's CONNECT command since Irssi + has CONNECT command for other purposes. */ + +static void command_sconnect(const char *data, SILC_SERVER_REC *server) +{ + if (!IS_SILC_SERVER(server) || !server->connected) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Not connected to server"); + return; + } + + silc_command_exec(server, "CONNECT", data); + signal_stop(); +} + +static void event_text(const char *line, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + char *str; + + g_return_if_fail(line != NULL); + + if (!IS_SILC_ITEM(item)) + return; + + str = g_strdup_printf("%s %s", item->name, line); + signal_emit("command msg", 3, str, server, item); + g_free(str); + + signal_stop(); +} + +/* FILE command */ + +SILC_TASK_CALLBACK(silc_client_file_close_later) +{ + FtpSession ftp = (FtpSession)context; + + SILC_LOG_DEBUG(("Start")); + + silc_client_file_close(silc_client, ftp->conn, ftp->session_id); + silc_free(ftp->filepath); + silc_free(ftp); +} + +static void silc_client_file_monitor(SilcClient client, + SilcClientConnection conn, + SilcClientMonitorStatus status, + SilcClientFileError error, + uint64 offset, + uint64 filesize, + SilcClientEntry client_entry, + uint32 session_id, + const char *filepath, + void *context) +{ + SILC_SERVER_REC *server = (SILC_SERVER_REC *)context; + FtpSession ftp; + char fsize[32]; + + snprintf(fsize, sizeof(fsize) - 1, "%llu", ((filesize + 1023) / 1024)); + + silc_dlist_start(server->ftp_sessions); + while ((ftp = silc_dlist_get(server->ftp_sessions)) != SILC_LIST_END) { + if (ftp->session_id == session_id) { + if (!ftp->filepath && filepath) + ftp->filepath = strdup(filepath); + break; + } + } + + if (ftp == SILC_LIST_END) + return; + + if (status == SILC_CLIENT_FILE_MONITOR_ERROR) { + if (error == SILC_CLIENT_FILE_NO_SUCH_FILE) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_ERROR_NO_SUCH_FILE, + client_entry->nickname, + filepath ? filepath : "[N/A]"); + else if (error == SILC_CLIENT_FILE_PERMISSION_DENIED) + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_ERROR_PERMISSION_DENIED, + client_entry->nickname); + else + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_ERROR, client_entry->nickname); + silc_schedule_task_add(silc_client->schedule, 0, + silc_client_file_close_later, ftp, + 1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); + if (ftp == server->current_session) + server->current_session = NULL; + silc_dlist_del(server->ftp_sessions, ftp); + } + + if (status == SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_KEY_EXCHANGE, client_entry->nickname); + } + + /* Save some transmission data */ + if (offset && filesize) { + unsigned long delta = time(NULL) - ftp->starttime; + + ftp->percent = ((double)offset / (double)filesize) * (double)100.0; + if (delta) + ftp->kps = (double)((offset / (double)delta) + 1023) / (double)1024; + else + ftp->kps = (double)(offset + 1023) / (double)1024; + ftp->offset = offset; + ftp->filesize = filesize; + } + + if (status == SILC_CLIENT_FILE_MONITOR_SEND) { + if (offset == 0) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_TRANSMIT, filepath, fsize, + client_entry->nickname); + ftp->starttime = time(NULL); + } + if (offset == filesize) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_TRANSMITTED, filepath, fsize, + client_entry->nickname, ftp->kps); + silc_schedule_task_add(silc_client->schedule, 0, + silc_client_file_close_later, ftp, + 1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); + if (ftp == server->current_session) + server->current_session = NULL; + silc_dlist_del(server->ftp_sessions, ftp); + } + } + + if (status == SILC_CLIENT_FILE_MONITOR_RECEIVE) { + if (offset == 0) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_RECEIVE, filepath, fsize, + client_entry->nickname); + ftp->starttime = time(NULL); + } + + if (offset == filesize) { + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_RECEIVED, filepath, fsize, + client_entry->nickname, ftp->kps); + silc_schedule_task_add(silc_client->schedule, 0, + silc_client_file_close_later, ftp, + 1, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); + if (ftp == server->current_session) + server->current_session = NULL; + silc_dlist_del(server->ftp_sessions, ftp); + } + } +} + +typedef struct { + SILC_SERVER_REC *server; + char *data; + char *nick; + WI_ITEM_REC *item; +} *FileGetClients; + +static void silc_client_command_file_get_clients(SilcClient client, + SilcClientConnection conn, + SilcClientEntry *clients, + uint32 clients_count, + void *context) +{ + FileGetClients internal = (FileGetClients)context; + + if (!clients) { + printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Unknown nick: %s", + internal->nick); + silc_free(internal->data); + silc_free(internal->nick); + silc_free(internal); + return; + } + + signal_emit("command file", 3, internal->data, internal->server, + internal->item); + + silc_free(internal->data); + silc_free(internal->nick); + silc_free(internal); +} + +static void command_file(const char *data, SILC_SERVER_REC *server, + WI_ITEM_REC *item) +{ + SilcClientConnection conn; + SilcClientEntry *entrys, client_entry; + SilcClientFileError ret; + uint32 entry_count; + char *nickname = NULL, *tmp; + unsigned char **argv; + uint32 argc; + uint32 *argv_lens, *argv_types; + int type = 0; + FtpSession ftp; + char *local_ip = NULL; + uint32 local_port = 0; + + if (!server || !IS_SILC_SERVER(server) || !server->connected) + cmd_return_error(CMDERR_NOT_CONNECTED); + + conn = server->conn; + + /* Now parse all arguments */ + tmp = g_strconcat("FILE", " ", data, NULL); + silc_parse_command_line(tmp, &argv, &argv_lens, &argv_types, &argc, 6); + g_free(tmp); + + if (argc == 1) + type = 4; + + if (argc >= 2) { + if (!strcasecmp(argv[1], "send")) + type = 1; + if (!strcasecmp(argv[1], "receive")) + type = 2; + if (!strcasecmp(argv[1], "close")) + type = 3; + } + + if (type == 0) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + switch (type) { + case 1: + if (argc < 4) + cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); + + /* Parse the typed nickname. */ + if (!silc_parse_userfqdn(argv[3], &nickname, NULL)) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_BAD_NICK, argv[3]); + goto out; + } + + /* Find client entry */ + entrys = silc_client_get_clients_local(silc_client, conn, nickname, + argv[3], &entry_count); + if (!entrys) { + FileGetClients inter = silc_calloc(1, sizeof(*inter)); + inter->server = server; + inter->data = strdup(data); + inter->nick = strdup(nickname); + inter->item = item; + silc_client_get_clients(silc_client, conn, nickname, argv[3], + silc_client_command_file_get_clients, inter); + goto out; + } + client_entry = entrys[0]; + silc_free(entrys); + + if (argc >= 5) + local_ip = argv[4]; + if (argc >= 6) + local_port = atoi(argv[5]); + + ftp = silc_calloc(1, sizeof(*ftp)); + ftp->session_id = + silc_client_file_send(silc_client, conn, silc_client_file_monitor, + server, local_ip, local_port, + client_entry, argv[2]); + + printformat_module("fe-common/silc", NULL, NULL, MSGLEVEL_CRAP, + SILCTXT_FILE_SEND, client_entry->nickname, + argv[2]); + + ftp->client_entry = client_entry; + ftp->filepath = strdup(argv[2]); + ftp->conn = conn; + ftp->send = TRUE; + silc_dlist_add(server->ftp_sessions, ftp); + server->current_session = ftp; + + break; + + case 2: + /* Parse the typed nickname. */ + if (argc >= 3) { + if (!silc_parse_userfqdn(argv[2], &nickname, NULL)) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_BAD_NICK, argv[2]); + goto out; + } + + /* Find client entry */ + entrys = silc_client_get_clients_local(silc_client, conn, nickname, + argv[2], &entry_count); + if (!entrys) { + FileGetClients inter = silc_calloc(1, sizeof(*inter)); + inter->server = server; + inter->data = strdup(data); + inter->nick = strdup(nickname); + inter->item = item; + silc_client_get_clients(silc_client, conn, nickname, argv[2], + silc_client_command_file_get_clients, inter); + goto out; + } + client_entry = entrys[0]; + silc_free(entrys); + } else { + if (!server->current_session) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_NA); + goto out; + } + + ret = silc_client_file_receive(silc_client, conn, + silc_client_file_monitor, server, + server->current_session->session_id); + if (ret != SILC_CLIENT_FILE_OK) { + if (ret == SILC_CLIENT_FILE_ALREADY_STARTED) + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_ALREADY_STARTED, + server->current_session->client_entry->nickname); + else + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_CLIENT_NA, + server->current_session->client_entry->nickname); + } + + goto out; + } + + silc_dlist_start(server->ftp_sessions); + while ((ftp = silc_dlist_get(server->ftp_sessions)) != SILC_LIST_END) { + if (ftp->client_entry == client_entry && !ftp->filepath) { + ret = silc_client_file_receive(silc_client, conn, + silc_client_file_monitor, server, + ftp->session_id); + if (ret != SILC_CLIENT_FILE_OK) { + if (ret == SILC_CLIENT_FILE_ALREADY_STARTED) + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_ALREADY_STARTED, + client_entry->nickname); + else + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_CLIENT_NA, + client_entry->nickname); + } + break; + } + } + + if (ftp == SILC_LIST_END) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_CLIENT_NA, + client_entry->nickname); + goto out; + } + break; + + case 3: + /* Parse the typed nickname. */ + if (argc >= 3) { + if (!silc_parse_userfqdn(argv[2], &nickname, NULL)) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_BAD_NICK, argv[2]); + goto out; + } + + /* Find client entry */ + entrys = silc_client_get_clients_local(silc_client, conn, nickname, + argv[2], &entry_count); + if (!entrys) { + FileGetClients inter = silc_calloc(1, sizeof(*inter)); + inter->server = server; + inter->data = strdup(data); + inter->nick = strdup(nickname); + inter->item = item; + silc_client_get_clients(silc_client, conn, nickname, argv[2], + silc_client_command_file_get_clients, inter); + goto out; + } + client_entry = entrys[0]; + silc_free(entrys); + } else { + if (!server->current_session) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_NA); + goto out; + } + + silc_client_file_close(silc_client, conn, + server->current_session->session_id); + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_CLOSED, + server->current_session->client_entry->nickname, + server->current_session->filepath ? + server->current_session->filepath : "[N/A]"); + silc_dlist_del(server->ftp_sessions, server->current_session); + silc_free(server->current_session->filepath); + silc_free(server->current_session); + server->current_session = NULL; + goto out; + } + + silc_dlist_start(server->ftp_sessions); + while ((ftp = silc_dlist_get(server->ftp_sessions)) != SILC_LIST_END) { + if (ftp->client_entry == client_entry) { + silc_client_file_close(silc_client, conn, ftp->session_id); + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_CLOSED, + client_entry->nickname, + ftp->filepath ? ftp->filepath : "[N/A]"); + if (ftp == server->current_session) + server->current_session = NULL; + silc_dlist_del(server->ftp_sessions, ftp); + silc_free(ftp->filepath); + silc_free(ftp); + break; + } + } + + if (ftp == SILC_LIST_END) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_CLIENT_NA, + client_entry->nickname); + goto out; + } + break; + + case 4: + + if (!silc_dlist_count(server->ftp_sessions)) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_NA); + goto out; + } + + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_SHOW_HEADER); + + silc_dlist_start(server->ftp_sessions); + while ((ftp = silc_dlist_get(server->ftp_sessions)) != SILC_LIST_END) { + printformat_module("fe-common/silc", server, NULL, + MSGLEVEL_CRAP, SILCTXT_FILE_SHOW_LINE, + ftp->client_entry->nickname, + ftp->send ? "send" : "receive", + (uint32)(ftp->offset + 1023) / 1024, + (uint32)(ftp->filesize + 1023) / 1024, + ftp->percent, ftp->kps, + ftp->filepath ? ftp->filepath : "[N/A]"); + } + + break; + + default: + break; + } + + out: + silc_free(nickname); +} + +void silc_server_init(void) +{ + silc_servers_reconnect_init(); + + signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); + signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_add("send text", (SIGNAL_FUNC) event_text); + command_bind("whois", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("whowas", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("nick", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("topic", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("cmode", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("cumode", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("users", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("list", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("ban", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("oper", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("silcoper", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("umode", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("invite", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("kill", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("kick", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("info", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("ping", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("motd", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("close", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("shutdown", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("getkey", MODULE_NAME, (SIGNAL_FUNC) command_self); + command_bind("sconnect", MODULE_NAME, (SIGNAL_FUNC) command_sconnect); + command_bind("file", MODULE_NAME, (SIGNAL_FUNC) command_file); + + command_set_options("connect", "+silcnet"); +} + +void silc_server_deinit(void) +{ + silc_servers_reconnect_deinit(); + + signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); + signal_remove("send text", (SIGNAL_FUNC) event_text); + command_unbind("whois", (SIGNAL_FUNC) command_self); + command_unbind("whowas", (SIGNAL_FUNC) command_self); + command_unbind("nick", (SIGNAL_FUNC) command_self); + command_unbind("topic", (SIGNAL_FUNC) command_self); + command_unbind("cmode", (SIGNAL_FUNC) command_self); + command_unbind("cumode", (SIGNAL_FUNC) command_self); + command_unbind("users", (SIGNAL_FUNC) command_self); + command_unbind("list", (SIGNAL_FUNC) command_self); + command_unbind("oper", (SIGNAL_FUNC) command_self); + command_unbind("silcoper", (SIGNAL_FUNC) command_self); + command_unbind("umode", (SIGNAL_FUNC) command_self); + command_unbind("invite", (SIGNAL_FUNC) command_self); + command_unbind("kill", (SIGNAL_FUNC) command_self); + command_unbind("kick", (SIGNAL_FUNC) command_self); + command_unbind("info", (SIGNAL_FUNC) command_self); + command_unbind("ping", (SIGNAL_FUNC) command_self); + command_unbind("motd", (SIGNAL_FUNC) command_self); + command_unbind("ban", (SIGNAL_FUNC) command_self); + command_unbind("close", (SIGNAL_FUNC) command_self); + command_unbind("shutdown", (SIGNAL_FUNC) command_self); + command_unbind("getkey", (SIGNAL_FUNC) command_self); + command_unbind("sconnect", (SIGNAL_FUNC) command_sconnect); + command_unbind("file", (SIGNAL_FUNC) command_file); +} + +void silc_server_free_ftp(SILC_SERVER_REC *server, + SilcClientEntry client_entry) +{ + FtpSession ftp; + + silc_dlist_start(server->ftp_sessions); + while ((ftp = silc_dlist_get(server->ftp_sessions)) != SILC_LIST_END) { + if (ftp->client_entry == client_entry) { + silc_dlist_del(server->ftp_sessions, ftp); + silc_free(ftp->filepath); + silc_free(ftp); + } + } +} diff --git a/apps/irssi/src/silc/core/silc-servers.h b/apps/irssi/src/silc/core/silc-servers.h new file mode 100644 index 00000000..7add2f55 --- /dev/null +++ b/apps/irssi/src/silc/core/silc-servers.h @@ -0,0 +1,73 @@ +#ifndef __SILC_SERVER_H +#define __SILC_SERVER_H + +#include "chat-protocols.h" +#include "servers.h" + +/* returns SILC_SERVER_REC if it's SILC server, NULL if it isn't */ +#define SILC_SERVER(server) \ + PROTO_CHECK_CAST(SERVER(server), SILC_SERVER_REC, chat_type, "SILC") +#define SILC_SERVER_CONNECT(conn) \ + PROTO_CHECK_CAST(SERVER_CONNECT(conn), SILC_SERVER_CONNECT_REC, \ + chat_type, "SILC") +#define IS_SILC_SERVER(server) \ + (SILC_SERVER(server) ? TRUE : FALSE) +#define IS_SILC_SERVER_CONNECT(conn) \ + (SILC_SERVER_CONNECT(conn) ? TRUE : FALSE) + +/* all strings should be either NULL or dynamically allocated */ +/* address and nick are mandatory, rest are optional */ +typedef struct { +#include "server-connect-rec.h" +} SILC_SERVER_CONNECT_REC; + +typedef struct { + SilcClientEntry client_entry; + SilcClientConnection conn; + uint32 session_id; + char *filepath; + bool send; + + long starttime; /* Start time of transfer */ + double kps; /* Kilos per second */ + uint64 offset; /* Current offset */ + uint64 filesize; /* Total file size */ + uint32 percent; /* Percent of current transmission */ +} *FtpSession; + +#define STRUCT_SERVER_CONNECT_REC SILC_SERVER_CONNECT_REC +typedef struct { +#include "server-rec.h" + /* Command sending queue */ + int cmdcount; /* number of commands in `cmdqueue'. Can be more than + there actually is, to make flood control remember + how many messages can be sent before starting the + flood control */ + int cmd_last_split; /* Last command wasn't sent entirely to server. + First item in `cmdqueue' should be re-sent. */ + GSList *cmdqueue; + GTimeVal last_cmd; /* last time command was sent to server */ + + GSList *idles; /* Idle queue - send these commands to server + if there's nothing else to do */ + + SilcDList ftp_sessions; + FtpSession current_session; + + gpointer chanqueries; + SilcClientConnection conn; +} SILC_SERVER_REC; + +SILC_SERVER_REC *silc_server_connect(SILC_SERVER_CONNECT_REC *conn); + +/* Return a string of all channels in server in server->channels_join() + format */ +char *silc_server_get_channels(SILC_SERVER_REC *server); +void silc_command_exec(SILC_SERVER_REC *server, + const char *command, const char *args); +void silc_server_init(void); +void silc_server_deinit(void); +void silc_server_free_ftp(SILC_SERVER_REC *server, + SilcClientEntry client_entry); + +#endif diff --git a/apps/irssi/src/silc/silc.c b/apps/irssi/src/silc/silc.c deleted file mode 100644 index de2bde07..00000000 --- a/apps/irssi/src/silc/silc.c +++ /dev/null @@ -1,4 +0,0 @@ -/* this file is automatically generated by configure - don't change */ -void silc_core_init(void); void silc_core_deinit(void); -void silc_init(void) { silc_core_init(); } -void silc_deinit(void) { silc_core_deinit(); } diff --git a/apps/silc/Makefile.am b/apps/silc/Makefile.am index 1b358e63..ced6a4b1 100644 --- a/apps/silc/Makefile.am +++ b/apps/silc/Makefile.am @@ -18,23 +18,21 @@ AUTOMAKE_OPTIONS = 1.0 no-dependencies foreign -bin_PROGRAMS = silc +#bin_PROGRAMS = silc +#silc_SOURCES = \ +# silc.c \ +# clientconfig.c \ +# clientutil.c \ +# local_command.c \ +# screen.c \ +# client_ops.c +#silc_DEPENDENCIES = ../lib/libsilcclient.a ../lib/libsilc.a -silc_SOURCES = \ - silc.c \ - client.c \ - command.c \ - command_reply.c \ - clientconfig.c \ - clientutil.c \ - protocol.c \ - screen.c +LIBS = $(SILC_COMMON_LIBS) +LDADD = -L. -L.. -L../lib -lsilcclient -LDADD = -L. -L.. -L../lib -lsilc -lcurses +ADD_INCLUDES = $(CURSES_INCLUDEDIR) EXTRA_DIST = *.h -INCLUDES = -I. -I.. -I../lib/silccore -I../lib/silccrypt \ - -I../lib/silcmath -I../lib/silcske -I../lib/silcsim \ - -I../includes \ - -I../lib/silcmath/gmp-3.0.1 +include $(top_srcdir)/Makefile.defines.in diff --git a/apps/silc/README b/apps/silc/README new file mode 100644 index 00000000..d8fe44b5 --- /dev/null +++ b/apps/silc/README @@ -0,0 +1,5 @@ +This directory includes the old SILC Client. It was the first SILC +client ever made. However, it is now obsolete and not supported. It +is provided here merely as an example code. Do not try to compile it. + + -Pekka diff --git a/apps/silc/client.c b/apps/silc/client.c deleted file mode 100644 index 80141797..00000000 --- a/apps/silc/client.c +++ /dev/null @@ -1,2371 +0,0 @@ -/* - - 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$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ - -#include "clientincludes.h" - -/* Static function prototypes */ -static int silc_client_bad_keys(unsigned char key); -static void silc_client_process_message(SilcClient client); -static char *silc_client_parse_command(unsigned char *buffer); - -/* Static task callback prototypes */ -SILC_TASK_CALLBACK(silc_client_update_clock); -SILC_TASK_CALLBACK(silc_client_run_commands); -SILC_TASK_CALLBACK(silc_client_process_key_press); -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); - -SilcClientWindow silc_client_create_main_window(SilcClient client); -SilcClientWindow silc_client_add_window(SilcClient client, - int is_current); -void silc_client_packet_parse_type(SilcClient client, - SilcSocketConnection sock, - SilcPacketContext *packet); -void silc_client_private_message_process(SilcClient client, - SilcSocketConnection sock, - SilcPacketContext *packet); - -/* Definitions from version.h */ -extern char *silc_version; -extern char *silc_name; -extern char *silc_fullname; - -/* 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. */ - -int silc_client_alloc(SilcClient *new_client) -{ - - *new_client = silc_calloc(1, sizeof(**new_client)); - if (*new_client == NULL) { - SILC_LOG_ERROR(("Could not allocate new client object")); - return FALSE; - } - - (*new_client)->input_buffer = NULL; - (*new_client)->screen = NULL; - (*new_client)->windows = NULL; - (*new_client)->windows_count = 0; - (*new_client)->current_win = NULL; - - return TRUE; -} - -/* 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")); - assert(client); - - client->username = silc_get_username(); - client->realname = silc_get_real_name(); - - /* Register all configured ciphers, PKCS and hash functions. */ - client->config->client = (void *)client; - silc_client_config_register_ciphers(client->config); - silc_client_config_register_pkcs(client->config); - silc_client_config_register_hashfuncs(client->config); - - /* 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 */ - -#if 0 - { - SilcCipher twofish; - unsigned char *src, *dst, *dec; - SilcBuffer packet; - int payload_len; - - payload_len = 4 + strlen("pekka riikonen"); - packet = silc_buffer_alloc(payload_len); - silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); - silc_buffer_format(packet, - SILC_STR_UI_SHORT(payload_len), - SILC_STR_UI_SHORT(SILC_SOCKET_TYPE_CLIENT), - SILC_STR_UI_XNSTRING("pekka riikonen", - strlen("pekka riikonen")), - SILC_STR_END); - - silc_cipher_alloc("twofish", &twofish); - twofish->cipher->set_key(twofish->context, "1234567890123456", 16); - twofish->set_iv(twofish, "6543210987654321"); - SILC_LOG_HEXDUMP(("source: len %d", packet->len), - packet->data, packet->len ); - silc_packet_encrypt(twofish, packet, packet->len); - SILC_LOG_HEXDUMP(("encrypted"), packet->data, packet->len); - silc_packet_decrypt(twofish, packet, packet->len); - SILC_LOG_HEXDUMP(("decrypted"), packet->data, packet->len); - - } - - { - SilcCipher cipher1, cipher2; - unsigned char *src, *dst, *dec; - int len = strlen("12345678901234561234567890123456123456789012345612345678901234561234567890123456"); - - src = silc_calloc(len + 1, sizeof(unsigned char)); - dst = silc_calloc(len + 1, sizeof(unsigned char)); - dec = silc_calloc(len + 1, sizeof(unsigned char)); - - memcpy(src, "12345678901234561234567890123456123456789012345612345678901234561234567890123456", len); - - silc_cipher_alloc("twofish", &cipher1); - cipher1->cipher->set_key(cipher1->context, "1234567890123456", 128); - cipher1->set_iv(cipher1, "6543210987654321"); - - silc_cipher_alloc("twofish", &cipher2); - cipher2->cipher->set_key(cipher2->context, "1234567890123456", 128); - cipher2->set_iv(cipher2, "6543210987654321"); - - SILC_LOG_HEXDUMP(("source: %d", len), src, len); - cipher1->cipher->encrypt(cipher1->context, src, src, len, cipher1->iv); - SILC_LOG_HEXDUMP(("encrypted"), src, len); - cipher2->set_iv(cipher2, "6543210987654321"); - cipher2->cipher->decrypt(cipher2->context, src, src, len, cipher2->iv); - SILC_LOG_HEXDUMP(("decrypted"), src, len); - - } -#endif - - /* Register the task queues. In SILC we have by default three task queues. - One task queue for non-timeout tasks which perform different kind of - I/O on file descriptors, timeout task queue for timeout tasks, and, - generic non-timeout task queue whose tasks apply to all connections. */ - silc_task_queue_alloc(&client->io_queue, TRUE); - if (!client->io_queue) { - goto err0; - } - silc_task_queue_alloc(&client->timeout_queue, TRUE); - if (!client->timeout_queue) { - goto err1; - } - silc_task_queue_alloc(&client->generic_queue, TRUE); - if (!client->generic_queue) { - goto err1; - } - - /* Initialize the scheduler */ - silc_schedule_init(client->io_queue, client->timeout_queue, - client->generic_queue, 5000); - - /* Register the main task that is used in client. This received - the key pressings. */ - if (silc_task_register(client->io_queue, fileno(stdin), - silc_client_process_key_press, - (void *)client, 0, 0, - SILC_TASK_FD, - SILC_TASK_PRI_NORMAL) == NULL) { - goto err2; - } - - /* Register timeout task that updates clock every minute. */ - if (silc_task_register(client->timeout_queue, 0, - silc_client_update_clock, - (void *)client, - silc_client_time_til_next_min(), 0, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_LOW) == NULL) { - goto err2; - } - - if (client->config->commands) { - /* Run user configured commands with timeout */ - if (silc_task_register(client->timeout_queue, 0, - silc_client_run_commands, - (void *)client, 0, 1, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_LOW) == NULL) { - goto err2; - } - } - - /* Allocate the input buffer used to save typed characters */ - client->input_buffer = silc_buffer_alloc(SILC_SCREEN_INPUT_WIN_SIZE); - silc_buffer_pull_tail(client->input_buffer, - SILC_BUFFER_END(client->input_buffer)); - - /* Initialize the screen */ - client->screen = silc_screen_init(); - silc_client_create_main_window(client); - client->screen->input_buffer = client->input_buffer->data; - silc_screen_print_coordinates(client->screen, 0); - - return TRUE; - - err0: - silc_task_queue_free(client->timeout_queue); - err1: - silc_task_queue_free(client->io_queue); - err2: - return FALSE; -} - -/* 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_LOG_DEBUG(("Client client")); -} - -/* 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(); -} - -/* Creates the main window used in SILC client. This is called always - at the initialization of the client. If user wants to create more - than one windows a new windows are always created by calling - silc_client_add_window. */ - -SilcClientWindow silc_client_create_main_window(SilcClient client) -{ - SilcClientWindow win; - void *screen; - - SILC_LOG_DEBUG(("Creating main window")); - - assert(client->screen != NULL); - - win = silc_calloc(1, sizeof(*win)); - if (win == NULL) { - SILC_LOG_ERROR(("Could not allocate new window")); - return NULL; - } - - client->screen->u_stat_line.program_name = silc_name; - client->screen->u_stat_line.program_version = silc_version; - - /* Add the pointers */ - win->nickname = silc_get_username(); - win->local_id = NULL; - win->local_id_data = NULL; - win->local_id_data_len = 0; - win->remote_host = NULL; - win->remote_port = -1; - win->sock = NULL; - - /* Create the actual screen */ - screen = (void *)silc_screen_create_output_window(client->screen); - silc_screen_create_input_window(client->screen); - silc_screen_init_upper_status_line(client->screen); - silc_screen_init_output_status_line(client->screen); - win->screen = screen; - - client->screen->bottom_line->nickname = win->nickname; - silc_screen_print_bottom_line(client->screen, 0); - - /* Add the window to windows table */ - client->windows = silc_calloc(1, sizeof(*client->windows)); - client->windows[client->windows_count] = win; - client->windows_count = 1; - - /* Automatically becomes the current active window */ - client->current_win = win; - - return win; -} - -/* Allocates and adds new window to the client. This allocates new - physical window and internal window for connection specific data. - All the connection specific data is always saved into a window - since connection is always associated to a active window. */ - -SilcClientWindow silc_client_add_window(SilcClient client, - int is_current) -{ - SilcClientWindow win; - - assert(client->screen != NULL); - - win = silc_calloc(1, sizeof(*win)); - if (win == NULL) { - SILC_LOG_ERROR(("Could not allocate new window")); - return NULL; - } - - /* Add the pointers */ - win->screen = silc_screen_add_output_window(client->screen); - win->sock = NULL; - - /* Add the window to windows table */ - client->windows = silc_realloc(client->windows, sizeof(*client->windows) - * (client->windows_count + 1)); - client->windows[client->windows_count] = win; - client->windows_count++; - - if (is_current == TRUE) - client->current_win = win; - - return win; -} - -/* The main task on SILC client. This processes the key pressings user - has made. */ - -SILC_TASK_CALLBACK(silc_client_process_key_press) -{ - SilcClient client = (SilcClient)context; - int c; - - /* There is data pending in stdin, this gets it directly */ - c = wgetch(client->screen->input_win); - if (silc_client_bad_keys(c)) - return; - - SILC_LOG_DEBUG(("Pressed key: %d", c)); - - switch(c) { - /* - * Special character handling - */ - case KEY_UP: - case KEY_DOWN: - break; - case KEY_RIGHT: - /* Right arrow */ - SILC_LOG_DEBUG(("RIGHT")); - silc_screen_input_cursor_right(client->screen); - break; - case KEY_LEFT: - /* Left arrow */ - SILC_LOG_DEBUG(("LEFT")); - silc_screen_input_cursor_left(client->screen); - break; - case KEY_BACKSPACE: - case KEY_DC: - case '\177': - case '\b': - /* Backspace */ - silc_screen_input_backspace(client->screen); - break; - case '\011': - /* Tabulator */ - break; - case KEY_IC: - /* Insert switch. Turns on/off insert on input window */ - silc_screen_input_insert(client->screen); - break; - case CTRL('j'): - case '\r': - /* Enter, Return. User pressed enter we are ready to - process the message. */ - silc_client_process_message(client); - silc_screen_input_reset(client->screen); - break; - case CTRL('l'): - /* Refresh screen, Ctrl^l */ - silc_screen_refresh_all(client->screen); - break; - case CTRL('a'): - case KEY_HOME: - case KEY_BEG: - /* Beginning, Home */ - silc_screen_input_cursor_home(client->screen); - break; - case CTRL('e'): - case KEY_END: - /* End */ - silc_screen_input_cursor_end(client->screen); - break; - case KEY_LL: - /* End */ - break; - case CTRL('g'): - /* Bell, Ctrl^g */ - beep(); - break; - case KEY_DL: - case CTRL('u'): - /* Delete line */ - break; - default: - /* - * Other characters - */ - if (c < 32) { - /* Control codes are printed as reversed */ - c = (c & 127) | 64; - wattron(client->screen->input_win, A_REVERSE); - silc_screen_input_print(client->screen, c); - wattroff(client->screen->input_win, A_REVERSE); - } else { - /* Normal character */ - silc_screen_input_print(client->screen, c); - } - } - - silc_screen_print_coordinates(client->screen, 0); - silc_screen_refresh_win(client->screen->input_win); -} - -static int silc_client_bad_keys(unsigned char key) -{ - /* these are explained in curses.h */ - switch(key) { - case KEY_SF: - case KEY_SR: - case KEY_NPAGE: - case KEY_PPAGE: - case KEY_PRINT: - case KEY_A1: - case KEY_A3: - case KEY_B2: - case KEY_C1: - case KEY_C3: - case KEY_UNDO: - case KEY_EXIT: - case '\v': /* VT */ - case '\E': /* we ignore ESC */ - return TRUE; - default: - return FALSE; - } -} - -/* Processes messages user has typed on the screen. This either sends - a packet out to network or if command were written executes it. */ - -static void silc_client_process_message(SilcClient client) -{ - unsigned char *data; - unsigned int len; - - SILC_LOG_DEBUG(("Start")); - - data = client->input_buffer->data; - len = strlen(data); - - if (data[0] == '/' && data[1] != ' ') { - /* Command */ - unsigned int argc = 0; - unsigned char **argv, *tmpcmd; - unsigned int *argv_lens, *argv_types; - SilcClientCommand *cmd; - SilcClientCommandContext ctx; - - /* Get the command */ - tmpcmd = silc_client_parse_command(data); - - /* Find command match */ - for (cmd = silc_command_list; cmd->name; cmd++) { - if (!strcmp(cmd->name, tmpcmd)) - break; - } - - if (cmd->name == NULL) { - silc_say(client, "Invalid command: %s", tmpcmd); - silc_free(tmpcmd); - goto out; - } - - /* Now parse all arguments */ - silc_client_parse_command_line(data, &argv, &argv_lens, - &argv_types, &argc, cmd->max_args); - silc_free(tmpcmd); - - SILC_LOG_DEBUG(("Exeuting command: %s", cmd->name)); - - /* Allocate command context. This and its internals must be free'd - by the command routine receiving it. */ - ctx = silc_calloc(1, sizeof(*ctx)); - ctx->client = client; - ctx->sock = client->current_win->sock; - ctx->argc = argc; - ctx->argv = argv; - ctx->argv_lens = argv_lens; - ctx->argv_types = argv_types; - - /* Execute command */ - (*cmd->cb)(ctx); - - } else { - /* Normal message to a channel */ - if (len && client->current_win->current_channel && - client->current_win->current_channel->on_channel == TRUE) { - silc_print(client, "> %s", data); - silc_client_packet_send_to_channel(client, - client->current_win->sock, - client->current_win->current_channel, - data, strlen(data), TRUE); - } - } - - out: - /* Clear the input buffer */ - silc_buffer_clear(client->input_buffer); - silc_buffer_pull_tail(client->input_buffer, - SILC_BUFFER_END(client->input_buffer)); -} - -/* Returns the command fetched from user typed command line */ - -static char *silc_client_parse_command(unsigned char *buffer) -{ - char *ret; - const char *cp = buffer; - int len; - - len = strcspn(cp, " "); - ret = silc_to_upper((char *)++cp); - ret[len - 1] = 0; - - return ret; -} - -/* Parses user typed command line. At most `max_args' is taken. Rest - of the line will be allocated as the last argument if there are more - than `max_args' arguments in the line. Note that the command name - is counted as one argument and is saved. */ - -void silc_client_parse_command_line(unsigned char *buffer, - unsigned char ***parsed, - unsigned int **parsed_lens, - unsigned int **parsed_types, - unsigned int *parsed_num, - unsigned int max_args) -{ - int i, len = 0; - int argc = 0; - const char *cp = buffer; - - /* Take the '/' away */ - cp++; - - *parsed = silc_calloc(1, sizeof(**parsed)); - *parsed_lens = silc_calloc(1, sizeof(**parsed_lens)); - - /* Get the command first */ - len = strcspn(cp, " "); - (*parsed)[0] = silc_to_upper((char *)cp); - (*parsed_lens)[0] = len; - cp += len + 1; - argc++; - - /* Parse arguments */ - if (strchr(cp, ' ') || strlen(cp) != 0) { - for (i = 1; i < max_args; i++) { - - if (i != max_args - 1) - len = strcspn(cp, " "); - else - len = strlen(cp); - - *parsed = silc_realloc(*parsed, sizeof(**parsed) * (argc + 1)); - *parsed_lens = silc_realloc(*parsed_lens, - sizeof(**parsed_lens) * (argc + 1)); - (*parsed)[argc] = silc_calloc(len + 1, sizeof(char)); - memcpy((*parsed)[argc], cp, len); - (*parsed_lens)[argc] = len; - argc++; - - cp += len; - if (strlen(cp) == 0) - break; - else - cp++; - } - } - - /* Save argument types. Protocol defines all argument types but - this implementation makes sure that they are always in correct - order hence this simple code. */ - *parsed_types = silc_calloc(argc, sizeof(**parsed_types)); - for (i = 0; i < argc; i++) - (*parsed_types)[i] = i; - - *parsed_num = argc; -} - -/* Updates clock on the screen every minute. */ - -SILC_TASK_CALLBACK(silc_client_update_clock) -{ - SilcClient client = (SilcClient)context; - - /* Update the clock on the screen */ - silc_screen_print_clock(client->screen); - - /* Re-register this same task */ - silc_task_register(qptr, 0, silc_client_update_clock, context, - silc_client_time_til_next_min(), 0, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_LOW); - - silc_screen_refresh_win(client->screen->input_win); -} - -/* Runs commands user configured in configuration file. This is - called when initializing client. */ - -SILC_TASK_CALLBACK(silc_client_run_commands) -{ - SilcClient client = (SilcClient)context; - SilcClientConfigSectionCommand *cs; - - SILC_LOG_DEBUG(("Start")); - - cs = client->config->commands; - while(cs) { - unsigned int argc = 0; - unsigned char **argv, *tmpcmd; - unsigned int *argv_lens, *argv_types; - SilcClientCommand *cmd; - SilcClientCommandContext ctx; - - /* Get the command */ - tmpcmd = silc_client_parse_command(cs->command); - - for (cmd = silc_command_list; cmd->name; cmd++) { - if (!strcmp(cmd->name, tmpcmd)) - break; - } - - if (cmd->name == NULL) { - silc_say(client, "Invalid command: %s", tmpcmd); - silc_free(tmpcmd); - continue; - } - - /* Now parse all arguments */ - silc_client_parse_command_line(cs->command, &argv, &argv_lens, - &argv_types, &argc, cmd->max_args); - silc_free(tmpcmd); - - SILC_LOG_DEBUG(("Exeuting command: %s", cmd->name)); - - /* Allocate command context. This and its internals must be free'd - by the command routine receiving it. */ - ctx = silc_calloc(1, sizeof(*ctx)); - ctx->client = client; - ctx->sock = client->current_win->sock; - ctx->argc = argc; - ctx->argv = argv; - ctx->argv_lens = argv_lens; - ctx->argv_types = argv_types; - - /* Execute command */ - (*cmd->cb)(ctx); - - cs = cs->next; - } -} - -/* Internal context for connection process. This is needed as we - doing asynchronous connecting. */ -typedef struct { - SilcClient client; - 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 */ - -int silc_client_connect_to_server(SilcClient client, int port, - char *host) -{ - SilcClientInternalConnectContext *ctx; - - SILC_LOG_DEBUG(("Connecting to port %d of server %s", - port, host)); - - silc_say(client, "Connecting to port %d of server %s", port, host); - - client->current_win->remote_host = strdup(host); - client->current_win->remote_port = port; - - /* 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->host = strdup(host); - ctx->port = port; - ctx->tries = 0; - - /* Do the actual connecting process */ - return silc_client_connect_to_server_internal(ctx); -} - -/* 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; - SilcProtocol protocol; - SilcClientKEInternalContext *proto_ctx; - 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 */ - silc_say(ctx->client, "Could not connect to server %s: %s", - ctx->host, strerror(opt)); - silc_say(client, "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 */ - silc_say(ctx->client, "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); - } - return; - } - - silc_schedule_unset_listen_fd(fd); - silc_task_unregister(client->io_queue, ctx->task); - silc_free(ctx); - - /* Allocate new socket connection object */ - silc_socket_alloc(fd, SILC_SOCKET_TYPE_SERVER, - (void *)client->current_win, - &client->current_win->sock); - if (client->current_win->sock == NULL) { - silc_say(client, "Error: Could not allocate connection socket"); - silc_net_close_connection(fd); - return; - } - client->current_win->sock->hostname = client->current_win->remote_host; - client->current_win->sock->port = client->current_win->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 = client->current_win->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) { - silc_say(client, "Error: Could not start authentication protocol"); - return; - } - client->current_win->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); -} - -/* 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->packet) - silc_buffer_free(ctx->packet); - if (ctx->ske) - silc_ske_free(ctx->ske); - silc_free(ctx); - sock->protocol = NULL; - 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 */ - - /* Resolve the authentication method to be used in this connection */ - proto_ctx->auth_meth = SILC_PROTOCOL_CONN_AUTH_NONE; - if (client->config->conns) { - SilcClientConfigSectionConnection *conn = NULL; - - /* Check if we find a match from user configured connections */ - conn = silc_client_config_find_connection(client->config, - sock->hostname, - sock->port); - if (conn) { - /* Match found. Use the configured authentication method */ - proto_ctx->auth_meth = conn->auth_meth; - if (conn->auth_data) { - proto_ctx->auth_data = strdup(conn->auth_data); - proto_ctx->auth_data_len = strlen(conn->auth_data); - } - } else { - /* No match found. Resolve by sending AUTH_REQUEST to server */ - proto_ctx->auth_meth = SILC_PROTOCOL_CONN_AUTH_NONE; - } - } else { - /* XXX Resolve by sending AUTH_REQUEST to 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; - SilcClientWindow win = (SilcClientWindow)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); - silc_free(ctx); - win->sock->protocol = NULL; - 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); - - silc_say(client, "Connected to port %d of host %s", - win->remote_port, win->remote_host); - - client->screen->bottom_line->connection = win->remote_host; - silc_screen_print_bottom_line(client->screen, 0); - - silc_protocol_free(protocol); - if (ctx->auth_data) - silc_free(ctx->auth_data); - if (ctx->ske) - silc_ske_free(ctx->ske); - silc_free(ctx); - win->sock->protocol = NULL; -} - -typedef struct { - SilcPacketContext *packetdata; - SilcSocketConnection sock; - SilcClient client; -} SilcClientInternalPacket; - -SILC_TASK_CALLBACK(silc_client_packet_process) -{ - SilcClient client = (SilcClient)context; - SilcSocketConnection sock = NULL; - int ret, packetlen, paddedlen; - - SILC_LOG_DEBUG(("Processing packet")); - - SILC_CLIENT_GET_SOCK(client, fd, sock); - if (sock == NULL) - return; - - /* 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); - - /* Write the packet out to the connection */ - ret = silc_packet_write(fd, sock->outbuf); - - /* If returned -2 could not write to connection now, will do - it later. */ - if (ret == -2) - return; - - /* Error */ - if (ret == -1) - SILC_LOG_ERROR(("Packet dropped")); - - /* 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); - - return; - } - - /* Packet receiving */ - if (type == SILC_TASK_READ) { - SILC_LOG_DEBUG(("Reading data from connection")); - - /* Allocate the incoming data buffer if not done already. */ - if (!sock->inbuf) - sock->inbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE); - - /* Read some data from connection */ - ret = silc_packet_read(fd, sock->inbuf); - - /* If returned -2 data was not available now, will read it later. */ - if (ret == -2) - return; - - /* Error */ - if (ret == -1) { - SILC_LOG_ERROR(("Packet dropped")); - 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)) { - silc_client_close_connection(client, sock); - return; - } - - silc_say(client, "Connection closed: premature EOF"); - SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock)); - - silc_client_close_connection(client, sock); - return; - } - - /* Check whether we received a whole packet. If reading went without - errors we either read a whole packet or the read packet is - incorrect and will be dropped. */ - SILC_PACKET_LENGTH(sock->inbuf, packetlen, paddedlen); - if (sock->inbuf->len < paddedlen || (packetlen < SILC_PACKET_MIN_LEN)) { - SILC_LOG_DEBUG(("Received incorrect packet, dropped")); - silc_buffer_clear(sock->inbuf); - return; - } - - /* Decrypt a packet coming from server connection */ - if (sock->type == SILC_SOCKET_TYPE_SERVER || - sock->type == SILC_SOCKET_TYPE_ROUTER) { - SilcClientWindow win = (SilcClientWindow)sock->user_data; - SilcClientInternalPacket *packet; - int mac_len = 0; - - if (win->hmac) - mac_len = win->hmac->hash->hash->hash_len; - - if (sock->inbuf->len - 2 > (paddedlen + mac_len)) { - /* Received possibly many packets at once */ - - while(sock->inbuf->len > 0) { - SILC_PACKET_LENGTH(sock->inbuf, packetlen, paddedlen); - if (sock->inbuf->len < paddedlen) { - SILC_LOG_DEBUG(("Received incorrect packet, dropped")); - return; - } - - paddedlen += 2; - packet = silc_calloc(1, sizeof(*packet)); - packet->client = client; - packet->sock = sock; - packet->packetdata = silc_calloc(1, sizeof(*packet->packetdata)); - packet->packetdata->buffer = silc_buffer_alloc(paddedlen + mac_len); - silc_buffer_pull_tail(packet->packetdata->buffer, - SILC_BUFFER_END(packet->packetdata->buffer)); - silc_buffer_put(packet->packetdata->buffer, sock->inbuf->data, - paddedlen + mac_len); - - SILC_LOG_HEXDUMP(("Incoming packet, len %d", - packet->packetdata->buffer->len), - packet->packetdata->buffer->data, - packet->packetdata->buffer->len); - SILC_LOG_DEBUG(("Packet from server %s, " - "server type %d, packet length %d", - win->remote_host, win->remote_type, paddedlen)); - - /* If this packet is for the current active connection we will - parse the packet right away to get it quickly on the screen. - Otherwise, it will be parsed with a timeout as the data is - for inactive window (which might not be visible at all). */ - if (SILC_CLIENT_IS_CURRENT_WIN(client, win)) { - /* Parse it real soon */ - silc_task_register(client->timeout_queue, fd, - silc_client_packet_parse, - (void *)packet, 0, 1, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); - } else { - /* Parse the packet with timeout */ - silc_task_register(client->timeout_queue, fd, - silc_client_packet_parse, - (void *)packet, 0, 200000, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); - } - - /* Pull the packet from inbuf thus we'll get the next one - in the inbuf. */ - silc_buffer_pull(sock->inbuf, paddedlen); - if (win->hmac) - silc_buffer_pull(sock->inbuf, mac_len); - } - silc_buffer_clear(sock->inbuf); - return; - } else { - /* Received one packet */ - - SILC_LOG_HEXDUMP(("An incoming packet, len %d", sock->inbuf->len), - sock->inbuf->data, sock->inbuf->len); - SILC_LOG_DEBUG(("Packet from server %s, " - "server type %d, packet length %d", - win->remote_host, win->remote_type, paddedlen)); - - packet = silc_calloc(1, sizeof(*packet)); - packet->client = client; - packet->sock = sock; - packet->packetdata = silc_calloc(1, sizeof(*packet->packetdata)); - packet->packetdata->buffer = silc_buffer_copy(sock->inbuf); - silc_buffer_clear(sock->inbuf); - - /* If this packet is for the current active connection we will - parse the packet right away to get it quickly on the screen. - Otherwise, it will be parsed with a timeout as the data is - for inactive window (which might not be visible at all). */ - if (SILC_CLIENT_IS_CURRENT_WIN(client, win)) { - /* Parse it real soon */ - silc_task_register(client->timeout_queue, fd, - silc_client_packet_parse, - (void *)packet, 0, 1, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); - return; - } else { - /* Parse the packet with timeout */ - silc_task_register(client->timeout_queue, fd, - silc_client_packet_parse, - (void *)packet, 0, 200000, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); - return; - } - } - } - } - - SILC_LOG_ERROR(("Weird, nothing happened - ignoring")); -} - -/* Checks MAC in the packet. Returns TRUE if MAC is Ok. This is called - after packet has been totally decrypted and parsed. */ - -static int silc_client_packet_check_mac(SilcClient client, - SilcSocketConnection sock, - SilcBuffer buffer) -{ - SilcClientWindow win = (SilcClientWindow)sock->user_data; - - /* Check MAC */ - if (win->hmac) { - int headlen = buffer->data - buffer->head, mac_len; - unsigned char *packet_mac, mac[32]; - - SILC_LOG_DEBUG(("Verifying MAC")); - - mac_len = win->hmac->hash->hash->hash_len; - - silc_buffer_push(buffer, headlen); - - /* Take mac from packet */ - packet_mac = buffer->tail; - - /* Make MAC and compare */ - memset(mac, 0, sizeof(mac)); - silc_hmac_make_with_key(win->hmac, - buffer->data, buffer->len, - win->hmac_key, win->hmac_key_len, mac); -#if 0 - SILC_LOG_HEXDUMP(("PMAC"), packet_mac, mac_len); - SILC_LOG_HEXDUMP(("CMAC"), mac, mac_len); -#endif - if (memcmp(mac, packet_mac, mac_len)) { - SILC_LOG_DEBUG(("MAC failed")); - return FALSE; - } - - SILC_LOG_DEBUG(("MAC is Ok")); - memset(mac, 0, sizeof(mac)); - - silc_buffer_pull(buffer, headlen); - } - - return TRUE; -} - -/* Decrypts rest of the packet (after decrypting just the SILC header). - After calling this function the packet is ready to be parsed by calling - silc_packet_parse. */ - -static int silc_client_packet_decrypt_rest(SilcClient client, - SilcSocketConnection sock, - SilcBuffer buffer) -{ - SilcClientWindow win = (SilcClientWindow)sock->user_data; - unsigned int mac_len = 0; - - /* Decrypt */ - if (win && win->receive_key) { - - /* Pull MAC from packet before decryption */ - if (win->hmac) { - mac_len = win->hmac->hash->hash->hash_len; - if ((buffer->len - mac_len) > SILC_PACKET_MIN_LEN) { - silc_buffer_push_tail(buffer, mac_len); - } else { - SILC_LOG_DEBUG(("Bad MAC length in packet, packet dropped")); - return FALSE; - } - } - - SILC_LOG_DEBUG(("Decrypting rest of the packet")); - - /* Decrypt rest of the packet */ - silc_buffer_pull(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - silc_packet_decrypt(win->receive_key, buffer, buffer->len); - silc_buffer_push(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - - SILC_LOG_HEXDUMP(("Fully decrypted packet, len %d", buffer->len), - buffer->data, buffer->len); - } - - return TRUE; -} - -/* Decrypts rest of the SILC Packet header that has been decrypted partly - already. This decrypts the padding of the packet also. After calling - this function the packet is ready to be parsed by calling function - silc_packet_parse. This is used in special packet reception. */ - -static int silc_client_packet_decrypt_rest_special(SilcClient client, - SilcSocketConnection sock, - SilcBuffer buffer) -{ - SilcClientWindow win = (SilcClientWindow)sock->user_data; - unsigned int mac_len = 0; - - /* Decrypt rest of the header plus padding */ - if (win && win->receive_key) { - unsigned short truelen, len1, len2, padlen; - - /* Pull MAC from packet before decryption */ - if (win->hmac) { - mac_len = win->hmac->hash->hash->hash_len; - if ((buffer->len - mac_len) > SILC_PACKET_MIN_LEN) { - silc_buffer_push_tail(buffer, mac_len); - } else { - SILC_LOG_DEBUG(("Bad MAC length in packet, packet dropped")); - return FALSE; - } - } - - SILC_LOG_DEBUG(("Decrypting rest of the header")); - - SILC_GET16_MSB(len1, &buffer->data[4]); - SILC_GET16_MSB(len2, &buffer->data[6]); - - truelen = SILC_PACKET_HEADER_LEN + len1 + len2; - padlen = SILC_PACKET_PADLEN(truelen); - len1 = (truelen + padlen) - (SILC_PACKET_MIN_HEADER_LEN - 2); - - silc_buffer_pull(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - SILC_LOG_HEXDUMP(("XXX"), buffer->data, buffer->len); - silc_packet_decrypt(win->receive_key, buffer, len1); - silc_buffer_push(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - SILC_LOG_HEXDUMP(("XXX"), buffer->data, buffer->len); - } - - return TRUE; -} - -/* Parses whole packet, received earlier. */ - -SILC_TASK_CALLBACK(silc_client_packet_parse) -{ - SilcClientInternalPacket *packet = (SilcClientInternalPacket *)context; - SilcBuffer buffer = packet->packetdata->buffer; - SilcClient client = packet->client; - SilcSocketConnection sock = packet->sock; - SilcClientWindow win = (SilcClientWindow)sock->user_data; - int ret; - - SILC_LOG_DEBUG(("Start")); - - /* Decrypt start of the packet header */ - if (win && win->receive_key) - silc_packet_decrypt(win->receive_key, buffer, SILC_PACKET_MIN_HEADER_LEN); - - /* If the packet type is not any special type lets decrypt rest - of the packet here. */ - if (buffer->data[3] != SILC_PACKET_CHANNEL_MESSAGE && - buffer->data[3] != SILC_PACKET_PRIVATE_MESSAGE) { - normal: - /* Normal packet, decrypt rest of the packet */ - if (!silc_client_packet_decrypt_rest(client, sock, buffer)) - goto out; - - /* Parse the packet. Packet type is returned. */ - ret = silc_packet_parse(packet->packetdata); - if (ret == SILC_PACKET_NONE) - goto out; - - /* Check MAC */ - if (!silc_client_packet_check_mac(client, sock, buffer)) - goto out; - } else { - /* If private message key is not set for private message it is - handled as normal packet. Go back up. */ - if (buffer->data[3] == SILC_PACKET_PRIVATE_MESSAGE && - !(buffer->data[2] & SILC_PACKET_FLAG_PRIVMSG_KEY)) - goto normal; - - /* Packet requires special handling, decrypt rest of the header. - This only decrypts. This does not do any MAC checking, it must - be done individually later when doing the special processing. */ - silc_client_packet_decrypt_rest_special(client, sock, buffer); - - /* Parse the packet header in special way as this is "special" - packet type. */ - ret = silc_packet_parse_special(packet->packetdata); - if (ret == SILC_PACKET_NONE) - goto out; - } - - /* Parse the incoming packet type */ - silc_client_packet_parse_type(client, sock, packet->packetdata); - - out: - silc_buffer_clear(packet->packetdata->buffer); - silc_free(packet->packetdata); - silc_free(packet); -} - -/* 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_ERROR; - sock->protocol->execute(client->timeout_queue, 0, - sock->protocol, sock->sock, 0, 0); - } - break; - case SILC_PACKET_REJECT: - break; - - case SILC_PACKET_NOTIFY: - /* - * Received notify message - */ - silc_client_notify_by_server(client, sock, buffer); - 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 - */ - { - SilcClientCommandReplyContext ctx; - ctx = silc_calloc(1, sizeof(*ctx)); - ctx->client = client; - ctx->sock = sock; - ctx->context = buffer; /* kludge */ - silc_client_command_reply_msg((void *)ctx); - } - 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, buffer); - break; - - case SILC_PACKET_KEY_EXCHANGE: - if (sock->protocol) { - SilcClientKEInternalContext *proto_ctx = - (SilcClientKEInternalContext *)sock->protocol->context; - - proto_ctx->packet = buffer; - - /* 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; - - /* 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. - */ - unsigned char *id_string; - unsigned short id_type; - - silc_buffer_unformat(buffer, - SILC_STR_UI_SHORT(&id_type), - SILC_STR_UI16_STRING_ALLOC(&id_string), - SILC_STR_END); - - if ((SilcIdType)id_type != SILC_ID_CLIENT) - break; - - silc_client_receive_new_id(client, sock, id_string); - silc_free(id_string); - break; - } - - default: - SILC_LOG_DEBUG(("Incorrect packet type %d, packet dropped", type)); - break; - } -} - -/* 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) -{ - /* Send now if forced to do so */ - if (force_send == TRUE) { - int ret; - SILC_LOG_DEBUG(("Forcing packet send, packet sent immediately")); - ret = silc_packet_write(sock->sock, sock->outbuf); - - if (ret == -1) - SILC_LOG_ERROR(("Packet dropped")); - if (ret != -2) - return ret; - - SILC_LOG_DEBUG(("Could not force the send, packet put to queue")); - } - - SILC_LOG_DEBUG(("Packet in queue")); - - /* 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; -} - -/* Prepare outgoing data buffer for packet sending. */ - -static void silc_client_packet_send_prepare(SilcClient client, - SilcSocketConnection sock, - unsigned int header_len, - unsigned int padlen, - unsigned int data_len) -{ - int totlen, oldlen; - - totlen = header_len + padlen + data_len; - - /* Prepare the outgoing buffer for packet sending. */ - if (!sock->outbuf) { - /* Allocate new buffer. This is done only once per connection. */ - SILC_LOG_DEBUG(("Allocating outgoing data buffer")); - - sock->outbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE); - silc_buffer_pull_tail(sock->outbuf, totlen); - silc_buffer_pull(sock->outbuf, header_len + padlen); - } else { - if (SILC_IS_OUTBUF_PENDING(sock)) { - /* There is some pending data in the buffer. */ - - if ((sock->outbuf->end - sock->outbuf->tail) < data_len) { - SILC_LOG_DEBUG(("Reallocating outgoing data buffer")); - /* XXX: not done yet */ - } - oldlen = sock->outbuf->len; - silc_buffer_pull_tail(sock->outbuf, totlen); - silc_buffer_pull(sock->outbuf, header_len + padlen + oldlen); - } else { - /* Buffer is free for use */ - silc_buffer_clear(sock->outbuf); - silc_buffer_pull_tail(sock->outbuf, totlen); - silc_buffer_pull(sock->outbuf, header_len + padlen); - } - } -} - -/* 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; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned char mac[32]; - unsigned int mac_len = 0; - - 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 && ((SilcClientWindow)sock->user_data)->send_key) - cipher = ((SilcClientWindow)sock->user_data)->send_key; - if (!hmac && ((SilcClientWindow)sock->user_data)->hmac) { - hmac = ((SilcClientWindow)sock->user_data)->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = ((SilcClientWindow)sock->user_data)->hmac_key; - hmac_key_len = ((SilcClientWindow)sock->user_data)->hmac_key_len; - } - if (!dst_id && ((SilcClientWindow)sock->user_data)->remote_id) { - dst_id = ((SilcClientWindow)sock->user_data)->remote_id; - dst_id_type = SILC_ID_SERVER; - } - } - - /* Set the packet context pointers */ - packetdata.flags = 0; - packetdata.type = type; - if (((SilcClientWindow)sock->user_data)->local_id_data) - packetdata.src_id = ((SilcClientWindow)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_client_packet_send_prepare(client, 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); - - /* Compute MAC of the packet */ - if (hmac) { - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - } - - /* Encrypt the packet */ - if (cipher) - silc_packet_encrypt(cipher, sock->outbuf, sock->outbuf->len); - - /* Pull MAC into the visible data area */ - if (hmac) - silc_buffer_pull_tail(sock->outbuf, mac_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; - SilcClientWindow win = (SilcClientWindow)sock->user_data; - SilcBuffer payload; - SilcPacketContext packetdata; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned char mac[32]; - unsigned int mac_len = 0; - unsigned char *id_string; - SilcCipher cipher; - SilcHmac hmac; - - SILC_LOG_DEBUG(("Sending packet to channel")); - - if (!channel || !channel->key) { - silc_say(client, "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_encode_payload(strlen(win->nickname), win->nickname, - data_len, data, 16, channel->iv, - client->rng); - if (!payload) { - silc_say(client, - "Error: Could not create packet to be sent to the 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 = win->send_key; - hmac = win->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = win->hmac_key; - hmac_key_len = win->hmac_key_len; - 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 = win->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_client_packet_send_prepare(client, 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); - - SILC_LOG_HEXDUMP(("XXX"), payload->data, payload->len); - - /* 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); - - /* Compute MAC of the packet */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - SILC_LOG_HEXDUMP(("XXX"), sock->outbuf->data, sock->outbuf->len); - - /* Encrypt the header and padding of the packet. This is encrypted - with normal session key shared with our server. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); - - 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) -{ - SilcClientWindow win = (SilcClientWindow)sock->user_data; - SilcBuffer buffer; - SilcPacketContext packetdata; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned char mac[32]; - unsigned int mac_len = 0; - unsigned int nick_len; - SilcCipher cipher; - SilcHmac hmac; - - SILC_LOG_DEBUG(("Sending private message")); - - /* Create private message payload */ - nick_len = strlen(client->current_win->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(client->current_win->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 = win->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = win->hmac_key; - hmac_key_len = win->hmac_key_len; - - /* Set the packet context pointers. */ - packetdata.flags = 0; - packetdata.type = SILC_PACKET_PRIVATE_MESSAGE; - packetdata.src_id = win->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 = win->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_client_packet_send_prepare(client, 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); - - /* Compute MAC of the packet */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - SILC_LOG_HEXDUMP(("XXX"), sock->outbuf->data, sock->outbuf->len); - - /* Encrypt the header and padding of the packet. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); - - 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) -{ - SilcClientWindow win; - int i; - - /* 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); - - silc_say(client, "Closed connection to host %s", sock->hostname ? - sock->hostname : sock->ip); - - /* Free everything */ - if (sock->user_data) { - win = (SilcClientWindow)sock->user_data; - - /* Clear ID caches */ - for (i = 0; i < 96; i++) - silc_idcache_del_all(&win->client_id_cache[i], - win->client_id_cache_count[i]); - for (i = 0; i < 96; i++) - silc_idcache_del_all(&win->channel_id_cache[i], - win->channel_id_cache_count[i]); - - /* Free data */ - if (win->remote_host) - silc_free(win->remote_host); - if (win->local_id) - silc_free(win->local_id); - if (win->local_id_data) - silc_free(win->local_id_data); - if (win->send_key) - silc_cipher_free(win->send_key); - if (win->receive_key) - silc_cipher_free(win->receive_key); - if (win->public_key) - silc_pkcs_free(win->public_key); - if (win->hmac) - silc_hmac_free(win->hmac); - if (win->hmac_key) { - memset(win->hmac_key, 0, win->hmac_key_len); - silc_free(win->hmac_key); - } - - win->sock = NULL; - win->remote_port = 0; - win->remote_type = 0; - win->send_key = NULL; - win->receive_key = NULL; - win->public_key = NULL; - win->hmac = NULL; - win->hmac_key = NULL; - win->hmac_key_len = 0; - win->local_id = NULL; - win->local_id_data = NULL; - win->remote_host = NULL; - } - - 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); - silc_say(client, 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); - silc_say(client, msg); - silc_free(msg); -} - -/* Received notify message from server */ - -void silc_client_notify_by_server(SilcClient client, - SilcSocketConnection sock, - SilcBuffer message) -{ - char *msg; - - msg = silc_calloc(message->len + 1, sizeof(char)); - memcpy(msg, message->data, message->len); - silc_say(client, msg); - silc_free(msg); -} - -/* 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, - unsigned char *id_string) -{ - SilcClientWindow win = (SilcClientWindow)sock->user_data; - char *nickname = win->nickname; - -#define CIDC(x) win->client_id_cache[(x) - 32] -#define CIDCC(x) win->client_id_cache_count[(x) - 32] - - /* Delete old ID from ID cache */ - silc_idcache_del_by_id(CIDC(nickname[0]), CIDCC(nickname[0]), - SILC_ID_CLIENT, win->local_id); - - /* Save the new ID */ - if (win->local_id) - silc_free(win->local_id); - win->local_id = silc_id_str2id(id_string, SILC_ID_CLIENT); - if (win->local_id_data) - silc_free(win->local_id_data); - win->local_id_data = - silc_calloc(SILC_ID_CLIENT_LEN, sizeof(unsigned char)); - memcpy(win->local_id_data, id_string, SILC_ID_CLIENT_LEN); - win->local_id_data_len = SILC_ID_CLIENT_LEN; - if (!win->local_entry) - win->local_entry = silc_calloc(1, sizeof(*win->local_entry)); - win->local_entry->nickname = win->nickname; - win->local_entry->id = win->local_id; - - /* Put it to the ID cache */ - CIDCC(nickname[0]) = silc_idcache_add(&CIDC(nickname[0]), - CIDCC(nickname[0]), - win->nickname, SILC_ID_CLIENT, - win->local_id, - (void *)win->local_entry); -#undef CIDC -#undef CIDCC -} - -/* 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 char *id_string) -{ - SilcClientWindow win = (SilcClientWindow)sock->user_data; - SilcChannelID *id; - SilcChannelEntry channel; - - SILC_LOG_DEBUG(("New channel ID")); - -#define CIDC(x) win->channel_id_cache[(x) - 32] -#define CIDCC(x) win->channel_id_cache_count[(x) - 32] - - id = silc_id_str2id(id_string, SILC_ID_CHANNEL); - channel = silc_calloc(1, sizeof(*channel)); - channel->channel_name = channel_name; - channel->id = id; - win->current_channel = channel; - - /* Put it to the ID cache */ - CIDCC(channel_name[0]) = silc_idcache_add(&CIDC(channel_name[0]), - CIDCC(channel_name[0]), - channel_name, SILC_ID_CHANNEL, - id, (void *)channel); -#undef CIDC -#undef CIDCC -} - -/* 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) -{ - int i; - unsigned char *id_string, *key, *cipher; - unsigned int key_len; - SilcClientWindow win = (SilcClientWindow)sock->user_data; - SilcChannelID *id; - SilcIDCache *id_cache = NULL; - SilcChannelEntry channel; - SilcChannelKeyPayload payload; - - SILC_LOG_DEBUG(("Received key for channel")); - -#define CIDC(x) win->channel_id_cache[(x)] -#define CIDCC(x) win->channel_id_cache_count[(x)] - - payload = silc_channel_key_parse_payload(packet); - if (!payload) - return; - - id_string = silc_channel_key_get_id(payload, NULL); - if (!id_string) { - silc_channel_key_free_payload(payload); - return; - } - id = silc_id_str2id(id_string, SILC_ID_CHANNEL); - - /* Find channel. XXX: This is bad and slow. */ - for (i = 0; i < 96; i++) { - if (CIDC(i) == NULL) - continue; - if (silc_idcache_find_by_id(CIDC(i), CIDCC(i), (void *)id, - SILC_ID_CHANNEL, &id_cache)) - break; - } - - if (!id_cache) - goto out; - - /* Save the key */ - key = silc_channel_key_get_key(payload, &key_len); - cipher = silc_channel_key_get_cipher(payload, NULL); - - channel = (SilcChannelEntry)id_cache->context; - channel->key_len = key_len; - channel->key = silc_calloc(key_len, sizeof(*channel->key)); - memcpy(channel->key, key, key_len); - - silc_cipher_alloc(cipher, &channel->channel_key); - if (!channel->channel_key) { - silc_say(client, "Cannot talk to channel: unsupported cipher %s", cipher); - goto out; - } - channel->channel_key->cipher->set_key(channel->channel_key->context, - key, key_len); - - /* Client is now joined to the channel */ - channel->on_channel = TRUE; - - out: - silc_free(id); - silc_channel_key_free_payload(payload); -#undef CIDC -#undef CIDCC -} - -/* 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) -{ - int i; - SilcClientWindow win = (SilcClientWindow)sock->user_data; - SilcBuffer buffer = packet->buffer; - SilcChannelPayload payload = NULL; - SilcChannelID *id = NULL; - SilcChannelEntry channel; - SilcIDCache *id_cache = NULL; - -#define CIDC(x) win->channel_id_cache[(x)] -#define CIDCC(x) win->channel_id_cache_count[(x)] - - /* Sanity checks */ - if (packet->dst_id_type != SILC_ID_CHANNEL) - goto out; - - id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); - - /* Find the channel entry from channels on this window */ - for (i = 0; i < 96; i++) { - if (CIDC(i) == NULL) - continue; - if (silc_idcache_find_by_id(CIDC(i), CIDCC(i), (void *)id, - SILC_ID_CHANNEL, &id_cache)) - break; - } - - if (!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_parse_payload(buffer); - if (!payload) - goto out; - - /* Display the message on screen */ - if (packet->src_id_type == SILC_ID_CLIENT) - /* Message from client */ - silc_print(client, "<%s> %s", silc_channel_get_nickname(payload, NULL), - silc_channel_get_data(payload, NULL)); - else - /* Message from server */ - silc_say(client, "%s", silc_channel_get_data(payload, NULL)); - - out: - if (id) - silc_free(id); - if (payload) - silc_channel_free_payload(payload); -#undef CIDC -#undef CIDCC -} diff --git a/apps/silc/client.h b/apps/silc/client.h deleted file mode 100644 index 661bd83a..00000000 --- a/apps/silc/client.h +++ /dev/null @@ -1,249 +0,0 @@ -/* - - client.h - - 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. - -*/ - -#ifndef CLIENT_H -#define CLIENT_H - -/* Window structure used in client to associate all the important - connection (window) specific data to this structure. How the window - actually appears on the screen in handeled by the silc_screen* - routines in screen.c. */ -typedef struct { - /* - * Local data - */ - char *nickname; - - /* Local client ID for this connection */ - SilcClientID *local_id; - - /* Decoded local ID so that the above defined ID would not have - to be decoded for every packet. */ - unsigned char *local_id_data; - unsigned int local_id_data_len; - - /* Own client entry. */ - SilcClientEntry local_entry; - - /* - * Remote data - */ - char *remote_host; - int remote_port; - int remote_type; - - /* Remote client ID for this connection */ - SilcClientID *remote_id; - - /* Remote local ID so that the above defined ID would not have - to be decoded for every packet. */ - unsigned char *remote_id_data; - unsigned int remote_id_data_len; - - /* - * Common data - */ - /* Keys */ - SilcCipher send_key; - SilcCipher receive_key; - SilcPKCS public_key; - SilcHmac hmac; - unsigned char *hmac_key; - unsigned int hmac_key_len; - - /* Client ID and Channel ID cache. Messages transmitted in SILC network - are done using different unique ID's. These are the cache for - thoses ID's used in the communication. */ - SilcIDCache *client_id_cache[96]; - unsigned int client_id_cache_count[96]; - SilcIDCache *channel_id_cache[96]; - unsigned int channel_id_cache_count[96]; - SilcIDCache *server_id_cache; - unsigned int server_id_cache_count; - - /* Current channel on window. All channel's are saved (allocated) into - the cache entries. */ - SilcChannelEntry current_channel; - - /* Socket connection object for this connection (window). This - object will have a back-pointer to this window object for fast - referencing (sock->user_data). */ - SilcSocketConnection sock; - - /* The actual physical screen. This data is handled by the - screen handling routines. */ - void *screen; -} *SilcClientWindow; - -typedef struct { - char *username; - char *realname; - - /* SILC client task queues */ - SilcTaskQueue io_queue; - SilcTaskQueue timeout_queue; - SilcTaskQueue generic_queue; - - /* Input buffer that holds the characters user types. This is - used only to store the typed chars for a while. */ - SilcBuffer input_buffer; - - /* Table of windows in client. All the data, including connection - specific data, is saved in here. */ - SilcClientWindow *windows; - unsigned int windows_count; - - /* Currently active window. This is pointer to the window table - defined above. This must never be free'd directly. */ - SilcClientWindow current_win; - - /* The SILC client screen object */ - SilcScreen screen; - - /* Generic cipher and hash objects */ - SilcCipher none_cipher; - SilcHash md5hash; - SilcHash sha1hash; - SilcHmac md5hmac; - SilcHmac sha1hmac; - - /* Configuration object */ - SilcClientConfig config; - - /* Random Number Generator */ - SilcRng rng; - -#ifdef SILC_SIM - /* SIM (SILC Module) table */ - SilcSimContext **sim; - unsigned int sim_count; -#endif -} SilcClientObject; - -typedef SilcClientObject *SilcClient; - -/* Macros */ - -#ifndef CTRL -#define CTRL(x) ((x) & 0x1f) /* Ctrl+x */ -#endif - -/* Registers generic task for file descriptor for reading from network and - writing to network. As being generic task the actual task is allocated - only once and after that the same task applies to all registered fd's. */ -#define SILC_CLIENT_REGISTER_CONNECTION_FOR_IO(fd) \ -do { \ - SilcTask tmptask = silc_task_register(client->generic_queue, (fd), \ - silc_client_packet_process, \ - context, 0, 0, \ - SILC_TASK_GENERIC, \ - SILC_TASK_PRI_NORMAL); \ - silc_task_set_iotype(tmptask, SILC_TASK_WRITE); \ -} while(0) - -#define SILC_CLIENT_SET_CONNECTION_FOR_INPUT(fd) \ -do { \ - silc_schedule_set_listen_fd((fd), (1L << SILC_TASK_READ)); \ -} while(0) \ - -#define SILC_CLIENT_SET_CONNECTION_FOR_OUTPUT(fd) \ -do { \ - silc_schedule_set_listen_fd((fd), ((1L << SILC_TASK_READ) | \ - (1L << SILC_TASK_WRITE))); \ -} while(0) - -/* Finds socket connection object by file descriptor */ -#define SILC_CLIENT_GET_SOCK(__x, __fd, __sock) \ -do { \ - int __i; \ - \ - for (__i = 0; __i < (__x)->windows_count; __i++) \ - if ((__x)->windows[__i]->sock->sock == (__fd)) \ - break; \ - \ - if (__i >= (__x)->windows_count) \ - (__sock) = NULL; \ - (__sock) = (__x)->windows[__i]->sock; \ -} while(0) - -/* Returns TRUE if windows is currently active window */ -#define SILC_CLIENT_IS_CURRENT_WIN(__x, __win) ((__x)->current_win == (__win)) - -/* Prototypes */ -int silc_client_alloc(SilcClient *new_client); -void silc_client_free(SilcClient client); -int silc_client_init(SilcClient client); -void silc_client_stop(SilcClient client); -void silc_client_run(SilcClient client); -void silc_client_parse_command_line(unsigned char *buffer, - unsigned char ***parsed, - unsigned int **parsed_lens, - unsigned int **parsed_types, - unsigned int *parsed_num, - unsigned int max_args); -int silc_client_connect_to_server(SilcClient client, int port, - char *host); -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); -void silc_client_packet_send_to_channel(SilcClient client, - SilcSocketConnection sock, - SilcChannelEntry channel, - unsigned char *data, - unsigned int data_len, - int force_send); -void silc_client_packet_send_private_message(SilcClient client, - SilcSocketConnection sock, - SilcClientEntry client_entry, - unsigned char *data, - unsigned int data_len, - int force_send); -void silc_client_close_connection(SilcClient client, - SilcSocketConnection sock); -void silc_client_disconnected_by_server(SilcClient client, - SilcSocketConnection sock, - SilcBuffer message); -void silc_client_error_by_server(SilcClient client, - SilcSocketConnection sock, - SilcBuffer message); -void silc_client_notify_by_server(SilcClient client, - SilcSocketConnection sock, - SilcBuffer message); -void silc_client_receive_new_id(SilcClient client, - SilcSocketConnection sock, - unsigned char *id_string); -void silc_client_new_channel_id(SilcClient client, - SilcSocketConnection sock, - char *channel_name, - unsigned char *id_string); -void silc_client_receive_channel_key(SilcClient client, - SilcSocketConnection sock, - SilcBuffer packet); -void silc_client_channel_message(SilcClient client, - SilcSocketConnection sock, - SilcPacketContext *packet); -#endif diff --git a/apps/silc/client_ops.c b/apps/silc/client_ops.c new file mode 100644 index 00000000..c027c147 --- /dev/null +++ b/apps/silc/client_ops.c @@ -0,0 +1,1342 @@ +/* + + client_ops.c + + Author: Pekka Riikonen + + Copyright (C) 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. + +*/ + +#include "clientincludes.h" + +static bool +silc_verify_public_key_internal(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type) +{ + int i; + char file[256], filename[256], *fingerprint; + struct passwd *pw; + struct stat st; + char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) ? + "server" : "client"); + + if (pk_type != SILC_SKE_PK_TYPE_SILC) { + silc_say(client, conn, "We don't support %s public key type %d", + entity, pk_type); + return FALSE; + } + + pw = getpwuid(getuid()); + if (!pw) + return FALSE; + + memset(filename, 0, sizeof(filename)); + memset(file, 0, sizeof(file)); + + if (conn_type == SILC_SOCKET_TYPE_SERVER || + conn_type == SILC_SOCKET_TYPE_ROUTER) { + snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity, + conn->sock->hostname, conn->sock->port); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + } else { + /* Replace all whitespaces with `_'. */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + for (i = 0; i < strlen(fingerprint); i++) + if (fingerprint[i] == ' ') + fingerprint[i] = '_'; + + snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint); + snprintf(filename, sizeof(filename) - 1, "%s/.silc/%skeys/%s", + pw->pw_dir, entity, file); + silc_free(fingerprint); + } + + /* Take fingerprint of the public key */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + + /* Check whether this key already exists */ + if (stat(filename, &st) < 0) { + + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + + /* Ask user to verify the key and save it */ + if (silc_client_ask_yes_no(client, + "Would you like to accept the key (y/n)? ")) + { + /* Save the key for future checking */ + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + } else { + /* The key already exists, verify it. */ + SilcPublicKey public_key; + unsigned char *encpk; + uint32 encpk_len; + + /* Load the key file */ + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_PEM)) + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_BIN)) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "Could not load your local copy of the %s key", + entity); + if (silc_client_ask_yes_no(client, + "Would you like to accept the key anyway (y/n)? ")) + { + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + + silc_free(fingerprint); + return FALSE; + } + + /* Encode the key data */ + encpk = silc_pkcs_public_key_encode(public_key, &encpk_len); + if (!encpk) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "Your local copy of the %s key is malformed", + entity); + if (silc_client_ask_yes_no(client, + "Would you like to accept the key anyway (y/n)? ")) + { + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + + silc_free(fingerprint); + return FALSE; + } + + if (memcmp(encpk, pk, encpk_len)) { + silc_say(client, conn, "Received %s public key", entity); + silc_say(client, conn, "Fingerprint for the %s key is", entity); + silc_say(client, conn, "%s", fingerprint); + silc_say(client, conn, "%s key does not match with your local copy", + entity); + silc_say(client, conn, + "It is possible that the key has expired or changed"); + silc_say(client, conn, "It is also possible that some one is performing " + "man-in-the-middle attack"); + + /* Ask user to verify the key and save it */ + if (silc_client_ask_yes_no(client, + "Would you like to accept the key anyway (y/n)? ")) + { + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + silc_free(fingerprint); + return TRUE; + } + + silc_say(client, conn, "Will not accept the %s key", entity); + silc_free(fingerprint); + return FALSE; + } + + /* Local copy matched */ + silc_free(fingerprint); + return TRUE; + } + + silc_say(client, conn, "Will not accept the %s key", entity); + silc_free(fingerprint); + return FALSE; +} + +void silc_say(SilcClient client, SilcClientConnection conn, + char *msg, ...) +{ + va_list vp; + char message[2048]; + SilcClientInternal app = (SilcClientInternal)client->application; + + memset(message, 0, sizeof(message)); + strncat(message, "\n*** ", 5); + + va_start(vp, msg); + vsprintf(message + 5, msg, vp); + va_end(vp); + + /* Print the message */ + silc_print_to_window(app->screen->output_win[0], message); +} + +/* Prints a message with three star (*) sign before the actual message + on the current output window. This is used to print command outputs + and error messages. */ + +void silc_op_say(SilcClient client, SilcClientConnection conn, + SilcClientMessageType type, char *msg, ...) +{ + va_list vp; + char message[2048]; + SilcClientInternal app = (SilcClientInternal)client->application; + + memset(message, 0, sizeof(message)); + strncat(message, "\n*** ", 5); + + va_start(vp, msg); + vsprintf(message + 5, msg, vp); + va_end(vp); + + /* Print the message */ + silc_print_to_window(app->screen->output_win[0], message); +} + +/* Message for a channel. The `sender' is the nickname of the sender + received in the packet. The `channel_name' is the name of the channel. */ + +void silc_channel_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcChannelEntry channel, + SilcMessageFlags flags, char *msg) +{ + /* Message from client */ + if (conn && !strcmp(conn->current_channel->channel_name, + channel->channel_name)) + if (flags & SILC_MESSAGE_FLAG_ACTION) + silc_print(client, "* %s %s", sender ? sender->nickname : "[]", + msg); + else if (flags & SILC_MESSAGE_FLAG_NOTICE) + silc_print(client, "- %s %s", sender ? sender->nickname : "[]", + msg); + else + silc_print(client, "<%s> %s", sender ? sender->nickname : "[]", + msg); + else + if (flags & SILC_MESSAGE_FLAG_ACTION) + silc_print(client, "* %s:%s %s", sender ? sender->nickname : + "[]", + channel->channel_name, msg); + else if (flags & SILC_MESSAGE_FLAG_NOTICE) + silc_print(client, "- %s:%s %s", sender ? sender->nickname : + "[]", + channel->channel_name, msg); + else + silc_print(client, "<%s:%s> %s", sender ? sender->nickname : + "[]", + channel->channel_name, msg); +} + +/* Private message to the client. The `sender' is the nickname of the + sender received in the packet. */ + +void silc_private_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, SilcMessageFlags flags, + char *msg) +{ + silc_print(client, "*%s* %s", sender->nickname, msg); +} + + +/* Notify message to the client. The notify arguments are sent in the + same order as servers sends them. The arguments are same as received + from the server except for ID's. If ID is received application receives + the corresponding entry to the ID. For example, if Client ID is received + application receives SilcClientEntry. Also, if the notify type is + for channel the channel entry is sent to application (even if server + does not send it). */ + +void silc_notify(SilcClient client, SilcClientConnection conn, + SilcNotifyType type, ...) +{ + SilcClientInternal app = (SilcClientInternal)client->application; + va_list vp; + char message[4096]; + SilcClientEntry client_entry, client_entry2; + SilcChannelEntry channel_entry; + char *tmp = NULL; + uint32 tmp_int; + + va_start(vp, type); + + memset(message, 0, sizeof(message)); + + /* Get arguments (defined by protocol in silc-pp-01 -draft) */ + switch(type) { + case SILC_NOTIFY_TYPE_NONE: + tmp = va_arg(vp, char *); + if (!tmp) + return; + strcpy(message, tmp); + break; + + case SILC_NOTIFY_TYPE_INVITE: + (void)va_arg(vp, SilcChannelEntry); + tmp = va_arg(vp, char *); + client_entry = va_arg(vp, SilcClientEntry); + snprintf(message, sizeof(message), "%s invites you to channel %s", + client_entry->nickname, tmp); + break; + + case SILC_NOTIFY_TYPE_JOIN: + client_entry = va_arg(vp, SilcClientEntry); + channel_entry = va_arg(vp, SilcChannelEntry); + snprintf(message, sizeof(message), "%s (%s) has joined channel %s", + client_entry->nickname, client_entry->username, + channel_entry->channel_name); + if (client_entry == conn->local_entry) { + SilcChannelUser chu; + + silc_list_start(channel_entry->clients); + while ((chu = silc_list_get(channel_entry->clients)) != SILC_LIST_END) { + if (chu->client == client_entry) { + if (app->screen->bottom_line->mode) + silc_free(app->screen->bottom_line->mode); + app->screen->bottom_line->mode = silc_client_chumode_char(chu->mode); + silc_screen_print_bottom_line(app->screen, 0); + break; + } + } + } + break; + + case SILC_NOTIFY_TYPE_LEAVE: + client_entry = va_arg(vp, SilcClientEntry); + channel_entry = va_arg(vp, SilcChannelEntry); + if (client_entry->server) + snprintf(message, sizeof(message), "%s@%s has left channel %s", + client_entry->nickname, client_entry->server, + channel_entry->channel_name); + else + snprintf(message, sizeof(message), "%s has left channel %s", + client_entry->nickname, channel_entry->channel_name); + break; + + case SILC_NOTIFY_TYPE_SIGNOFF: + client_entry = va_arg(vp, SilcClientEntry); + tmp = va_arg(vp, char *); + if (client_entry->server) + snprintf(message, sizeof(message), "Signoff: %s@%s %s%s%s", + client_entry->nickname, client_entry->server, + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + else + snprintf(message, sizeof(message), "Signoff: %s %s%s%s", + client_entry->nickname, + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + break; + + case SILC_NOTIFY_TYPE_TOPIC_SET: + client_entry = va_arg(vp, SilcClientEntry); + tmp = va_arg(vp, char *); + channel_entry = va_arg(vp, SilcChannelEntry); + if (client_entry->server) + snprintf(message, sizeof(message), "%s@%s set topic on %s: %s", + client_entry->nickname, client_entry->server, + channel_entry->channel_name, tmp); + else + snprintf(message, sizeof(message), "%s set topic on %s: %s", + client_entry->nickname, channel_entry->channel_name, tmp); + break; + + case SILC_NOTIFY_TYPE_NICK_CHANGE: + client_entry = va_arg(vp, SilcClientEntry); + client_entry2 = va_arg(vp, SilcClientEntry); + if (client_entry->server && client_entry2->server) + snprintf(message, sizeof(message), "%s@%s is known as %s@%s", + client_entry->nickname, client_entry->server, + client_entry2->nickname, client_entry2->server); + else + snprintf(message, sizeof(message), "%s is known as %s", + client_entry->nickname, client_entry2->nickname); + break; + + case SILC_NOTIFY_TYPE_CMODE_CHANGE: + client_entry = va_arg(vp, SilcClientEntry); + tmp_int = va_arg(vp, uint32); + (void)va_arg(vp, char *); + (void)va_arg(vp, char *); + channel_entry = va_arg(vp, SilcChannelEntry); + + tmp = silc_client_chmode(tmp_int, + channel_entry->channel_key->cipher->name, + channel_entry->hmac->hmac->name); + + if (tmp) { + if (client_entry) { + snprintf(message, sizeof(message), "%s changed channel mode to +%s", + client_entry->nickname, tmp); + } else { + snprintf(message, sizeof(message), + "channel mode was changed to +%s (forced by router)", + tmp); + } + } else { + if (client_entry) { + snprintf(message, sizeof(message), "%s removed all channel modes", + client_entry->nickname); + } else { + snprintf(message, sizeof(message), + "Removed all channel modes (forced by router)"); + } + } + + if (app->screen->bottom_line->channel_mode) + silc_free(app->screen->bottom_line->channel_mode); + app->screen->bottom_line->channel_mode = tmp; + silc_screen_print_bottom_line(app->screen, 0); + break; + + case SILC_NOTIFY_TYPE_CUMODE_CHANGE: + client_entry = va_arg(vp, SilcClientEntry); + tmp_int = va_arg(vp, uint32); + tmp = silc_client_chumode(tmp_int); + client_entry2 = va_arg(vp, SilcClientEntry); + channel_entry = va_arg(vp, SilcChannelEntry); + if (tmp) + snprintf(message, sizeof(message), "%s changed %s's mode to +%s", + client_entry->nickname, client_entry2->nickname, tmp); + else + snprintf(message, sizeof(message), "%s removed %s's modes", + client_entry->nickname, client_entry2->nickname); + if (client_entry2 == conn->local_entry) { + if (app->screen->bottom_line->mode) + silc_free(app->screen->bottom_line->mode); + app->screen->bottom_line->mode = silc_client_chumode_char(tmp_int); + silc_screen_print_bottom_line(app->screen, 0); + } + silc_free(tmp); + break; + + case SILC_NOTIFY_TYPE_MOTD: + { + char line[256]; + int i; + tmp = va_arg(vp, unsigned char *); + + i = 0; + while(tmp[i] != 0) { + if (tmp[i++] == '\n') { + memset(line, 0, sizeof(line)); + strncat(line, tmp, i - 1); + tmp += i; + + silc_say(client, conn, "%s", line); + + if (!strlen(tmp)) + break; + i = 0; + } + } + } + return; + + case SILC_NOTIFY_TYPE_CHANNEL_CHANGE: + return; + break; + + case SILC_NOTIFY_TYPE_KICKED: + client_entry = va_arg(vp, SilcClientEntry); + tmp = va_arg(vp, char *); + channel_entry = va_arg(vp, SilcChannelEntry); + + if (client_entry == conn->local_entry) { + snprintf(message, sizeof(message), + "You have been kicked off channel %s %s%s%s", + conn->current_channel->channel_name, + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + } else { + snprintf(message, sizeof(message), + "%s%s%s has been kicked off channel %s %s%s%s", + client_entry->nickname, + client_entry->server ? "@" : "", + client_entry->server ? client_entry->server : "", + conn->current_channel->channel_name, + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + } + break; + + case SILC_NOTIFY_TYPE_KILLED: + client_entry = va_arg(vp, SilcClientEntry); + tmp = va_arg(vp, char *); + channel_entry = va_arg(vp, SilcChannelEntry); + + if (client_entry == conn->local_entry) { + snprintf(message, sizeof(message), + "You have been killed from the SILC Network %s%s%s", + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + } else { + snprintf(message, sizeof(message), + "%s%s%s has been killed from the SILC Network %s%s%s", + client_entry->nickname, + client_entry->server ? "@" : "", + client_entry->server ? client_entry->server : "", + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + } + break; + + case SILC_NOTIFY_TYPE_SERVER_SIGNOFF: + { + SilcClientEntry *clients; + uint32 clients_count; + int i; + + (void)va_arg(vp, void *); + clients = va_arg(vp, SilcClientEntry *); + clients_count = va_arg(vp, uint32); + + for (i = 0; i < clients_count; i++) { + if (clients[i]->server) + snprintf(message, sizeof(message), "Server signoff: %s@%s %s%s%s", + clients[i]->nickname, clients[i]->server, + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + else + snprintf(message, sizeof(message), "Server signoff: %s %s%s%s", + clients[i]->nickname, + tmp ? "(" : "", tmp ? tmp : "", tmp ? ")" : ""); + silc_print(client, "*** %s", message); + memset(message, 0, sizeof(message)); + } + return; + } + + default: + break; + } + + silc_print(client, "*** %s", message); +} + +/* Command handler. This function is called always in the command function. + If error occurs it will be called as well. `conn' is the associated + client connection. `cmd_context' is the command context that was + originally sent to the command. `success' is FALSE if error occured + during command. `command' is the command being processed. It must be + noted that this is not reply from server. This is merely called just + after application has called the command. Just to tell application + that the command really was processed. */ + +void silc_command(SilcClient client, SilcClientConnection conn, + SilcClientCommandContext cmd_context, int success, + SilcCommand command) +{ + SilcClientInternal app = (SilcClientInternal)client->application; + + if (!success) + return; + + switch(command) + { + + case SILC_COMMAND_QUIT: + app->screen->bottom_line->channel = NULL; + silc_screen_print_bottom_line(app->screen, 0); + break; + + case SILC_COMMAND_LEAVE: + /* We won't talk anymore on this channel */ + silc_say(client, conn, "You have left channel %s", + conn->current_channel->channel_name); + break; + + } +} + +/* We've resolved all clients we don't know about, now just print the + users from the channel on the screen. */ + +void silc_client_show_users(SilcClient client, + SilcClientConnection conn, + SilcClientEntry *clients, + uint32 clients_count, + void *context) +{ + SilcChannelEntry channel = (SilcChannelEntry)context; + SilcChannelUser chu; + int k = 0, len1 = 0, len2 = 0; + char *name_list = NULL; + + if (!clients) + return; + + silc_list_start(channel->clients); + while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) { + char *m, *n = chu->client->nickname; + if (!n) + continue; + + len2 = strlen(n); + len1 += len2; + + name_list = silc_realloc(name_list, sizeof(*name_list) * (len1 + 3)); + + m = silc_client_chumode_char(chu->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 == silc_list_count(channel->clients) - 1) + break; + memcpy(name_list + len1, " ", 1); + len1++; + k++; + } + + silc_say(client, conn, "Users on %s: %s", channel->channel_name, + name_list); + silc_free(name_list); +} + +/* Command reply handler. This function is called always in the command reply + function. If error occurs it will be called as well. Normal scenario + is that it will be called after the received command data has been parsed + and processed. The function is used to pass the received command data to + the application. + + `conn' is the associated client connection. `cmd_payload' is the command + payload data received from server and it can be ignored. It is provided + if the application would like to re-parse the received command data, + however, it must be noted that the data is parsed already by the library + thus the payload can be ignored. `success' is FALSE if error occured. + In this case arguments are not sent to the application. `command' is the + command reply being processed. The function has variable argument list + and each command defines the number and type of arguments it passes to the + application (on error they are not sent). */ + +void silc_command_reply(SilcClient client, SilcClientConnection conn, + SilcCommandPayload cmd_payload, int success, + SilcCommand command, SilcCommandStatus status, ...) +{ + SilcClientInternal app = (SilcClientInternal)client->application; + SilcChannelUser chu; + va_list vp; + + va_start(vp, status); + + switch(command) + { + case SILC_COMMAND_WHOIS: + { + char buf[1024], *nickname, *username, *realname; + int len; + uint32 idle, mode; + SilcBuffer channels; + + if (status == SILC_STATUS_ERR_NO_SUCH_NICK || + status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) { + char *tmp; + tmp = silc_argument_get_arg_type(silc_command_get_args(cmd_payload), + 3, NULL); + if (tmp) + silc_say(client, conn, "%s: %s", tmp, + silc_client_command_status_message(status)); + else + silc_say(client, conn, "%s", + silc_client_command_status_message(status)); + break; + } + + if (!success) + return; + + (void)va_arg(vp, SilcClientEntry); + nickname = va_arg(vp, char *); + username = va_arg(vp, char *); + realname = va_arg(vp, char *); + channels = va_arg(vp, SilcBuffer); + mode = va_arg(vp, uint32); + idle = va_arg(vp, uint32); + + memset(buf, 0, sizeof(buf)); + + if (nickname) { + len = strlen(nickname); + strncat(buf, nickname, len); + strncat(buf, " is ", 4); + } + + if (username) { + strncat(buf, username, strlen(username)); + } + + if (realname) { + strncat(buf, " (", 2); + strncat(buf, realname, strlen(realname)); + strncat(buf, ")", 1); + } + + silc_say(client, conn, "%s", buf); + + if (channels) { + SilcDList list = silc_channel_payload_parse_list(channels); + if (list) { + SilcChannelPayload entry; + + memset(buf, 0, sizeof(buf)); + strcat(buf, "on channels: "); + + silc_dlist_start(list); + while ((entry = silc_dlist_get(list)) != SILC_LIST_END) { + char *m = silc_client_chumode_char(silc_channel_get_mode(entry)); + uint32 name_len; + char *name = silc_channel_get_name(entry, &name_len); + + if (m) + strncat(buf, m, strlen(m)); + strncat(buf, name, name_len); + strncat(buf, " ", 1); + silc_free(m); + } + + silc_say(client, conn, "%s", buf); + silc_channel_payload_list_free(list); + } + } + + if (mode) { + if ((mode & SILC_UMODE_SERVER_OPERATOR) || + (mode & SILC_UMODE_ROUTER_OPERATOR)) + silc_say(client, conn, "%s is %s", nickname, + (mode & SILC_UMODE_SERVER_OPERATOR) ? + "Server Operator" : + (mode & SILC_UMODE_ROUTER_OPERATOR) ? + "SILC Operator" : "[Unknown mode]"); + + if (mode & SILC_UMODE_GONE) + silc_say(client, conn, "%s is gone", nickname); + } + + if (idle && nickname) + silc_say(client, conn, "%s has been idle %d %s", + nickname, + idle > 60 ? (idle / 60) : idle, + idle > 60 ? "minutes" : "seconds"); + } + break; + + case SILC_COMMAND_WHOWAS: + { + char buf[1024], *nickname, *username, *realname; + int len; + + if (status == SILC_STATUS_ERR_NO_SUCH_NICK || + status == SILC_STATUS_ERR_NO_SUCH_CLIENT_ID) { + char *tmp; + tmp = silc_argument_get_arg_type(silc_command_get_args(cmd_payload), + 3, NULL); + if (tmp) + silc_say(client, conn, "%s: %s", tmp, + silc_client_command_status_message(status)); + else + silc_say(client, conn, "%s", + silc_client_command_status_message(status)); + break; + } + + if (!success) + return; + + (void)va_arg(vp, SilcClientEntry); + nickname = va_arg(vp, char *); + username = va_arg(vp, char *); + realname = va_arg(vp, char *); + + memset(buf, 0, sizeof(buf)); + + if (nickname) { + len = strlen(nickname); + strncat(buf, nickname, len); + strncat(buf, " was ", 5); + } + + if (username) { + strncat(buf, username, strlen(nickname)); + } + + if (realname) { + strncat(buf, " (", 2); + strncat(buf, realname, strlen(realname)); + strncat(buf, ")", 1); + } + + silc_say(client, conn, "%s", buf); + } + break; + + case SILC_COMMAND_INVITE: + { + SilcChannelEntry channel; + char *invite_list; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + invite_list = va_arg(vp, char *); + + if (invite_list) + silc_say(client, conn, "%s invite list: %s", channel->channel_name, + invite_list); + else + silc_say(client, conn, "%s invite list not set", + channel->channel_name); + } + break; + + case SILC_COMMAND_JOIN: + { + uint32 mode; + char *topic; + SilcBuffer client_id_list; + uint32 list_count; + SilcChannelEntry channel; + + if (!success) + return; + + app->screen->bottom_line->channel = va_arg(vp, char *); + channel = va_arg(vp, SilcChannelEntry); + mode = va_arg(vp, uint32); + (void)va_arg(vp, uint32); + (void)va_arg(vp, unsigned char *); + (void)va_arg(vp, unsigned char *); + (void)va_arg(vp, unsigned char *); + topic = va_arg(vp, char *); + (void)va_arg(vp, unsigned char *); + list_count = va_arg(vp, uint32); + client_id_list = va_arg(vp, SilcBuffer); + + if (topic) + silc_say(client, conn, "Topic for %s: %s", + app->screen->bottom_line->channel, topic); + + app->screen->bottom_line->channel_mode = + silc_client_chmode(mode, + channel->channel_key->cipher->name, + channel->hmac->hmac->name); + silc_screen_print_bottom_line(app->screen, 0); + + /* Resolve the client information */ + silc_client_get_clients_by_list(client, conn, list_count, + client_id_list, + silc_client_show_users, channel); + } + break; + + case SILC_COMMAND_NICK: + { + SilcClientEntry entry; + + if (!success) + return; + + entry = va_arg(vp, SilcClientEntry); + silc_say(client, conn, "Your current nickname is %s", entry->nickname); + app->screen->bottom_line->nickname = entry->nickname; + silc_screen_print_bottom_line(app->screen, 0); + } + break; + + case SILC_COMMAND_LIST: + { + char *topic, *name; + int usercount; + unsigned char buf[256], tmp[16]; + int i, len; + + if (!success) + return; + + (void)va_arg(vp, SilcChannelEntry); + name = va_arg(vp, char *); + topic = va_arg(vp, char *); + usercount = va_arg(vp, int); + + if (status == SILC_STATUS_LIST_START || + status == SILC_STATUS_OK) + silc_say(client, conn, + " Channel Users Topic"); + + memset(buf, 0, sizeof(buf)); + strncat(buf, " ", 2); + len = strlen(name); + strncat(buf, name, len > 40 ? 40 : len); + if (len < 40) + for (i = 0; i < 40 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + memset(tmp, 0, sizeof(tmp)); + if (usercount) { + snprintf(tmp, sizeof(tmp), "%d", usercount); + strcat(buf, tmp); + } + len = strlen(tmp); + if (len < 10) + for (i = 0; i < 10 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + if (topic) { + len = strlen(topic); + strncat(buf, topic, len); + } + + silc_say(client, conn, "%s", buf); + } + break; + + case SILC_COMMAND_UMODE: + { + uint32 mode; + + if (!success) + return; + + mode = va_arg(vp, uint32); + + if (!mode && app->screen->bottom_line->umode) { + silc_free(app->screen->bottom_line->umode); + app->screen->bottom_line->umode = NULL; + } + + if (mode & SILC_UMODE_SERVER_OPERATOR) { + if (app->screen->bottom_line->umode) + silc_free(app->screen->bottom_line->umode); + app->screen->bottom_line->umode = strdup("Server Operator");; + } + + if (mode & SILC_UMODE_ROUTER_OPERATOR) { + if (app->screen->bottom_line->umode) + silc_free(app->screen->bottom_line->umode); + app->screen->bottom_line->umode = strdup("SILC Operator");; + } + + silc_screen_print_bottom_line(app->screen, 0); + } + break; + + case SILC_COMMAND_OPER: + if (status == SILC_STATUS_OK) { + conn->local_entry->mode |= SILC_UMODE_SERVER_OPERATOR; + if (app->screen->bottom_line->umode) + silc_free(app->screen->bottom_line->umode); + app->screen->bottom_line->umode = strdup("Server Operator");; + silc_screen_print_bottom_line(app->screen, 0); + } + break; + + case SILC_COMMAND_SILCOPER: + if (status == SILC_STATUS_OK) { + conn->local_entry->mode |= SILC_UMODE_ROUTER_OPERATOR; + if (app->screen->bottom_line->umode) + silc_free(app->screen->bottom_line->umode); + app->screen->bottom_line->umode = strdup("SILC Operator");; + silc_screen_print_bottom_line(app->screen, 0); + } + break; + + case SILC_COMMAND_USERS: + { + SilcChannelEntry channel; + int line_len; + char *line; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + + /* There are two ways to do this, either parse the list (that + the command_reply sends (just take it with va_arg()) or just + traverse the channel's client list. I'll do the latter. See + JOIN command reply for example for the list. */ + + silc_say(client, conn, "Users on %s", channel->channel_name); + + line = silc_calloc(1024, sizeof(*line)); + line_len = 1024; + silc_list_start(channel->clients); + while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) { + SilcClientEntry e = chu->client; + int i, len1; + char *m, tmp[80]; + + memset(line, 0, line_len); + + if (chu->client == conn->local_entry) { + /* Update status line */ + if (app->screen->bottom_line->mode) + silc_free(app->screen->bottom_line->mode); + app->screen->bottom_line->mode = + silc_client_chumode_char(chu->mode); + silc_screen_print_bottom_line(app->screen, 0); + } + + if (strlen(e->nickname) + strlen(e->server) + 100 > line_len) { + silc_free(line); + line_len += strlen(e->nickname) + strlen(e->server) + 100; + line = silc_calloc(line_len, sizeof(*line)); + } + + memset(tmp, 0, sizeof(tmp)); + m = silc_client_chumode_char(chu->mode); + + strncat(line, " ", 1); + strncat(line, e->nickname, strlen(e->nickname)); + strncat(line, e->server ? "@" : "", 1); + + len1 = 0; + if (e->server) + len1 = strlen(e->server); + strncat(line, e->server ? e->server : "", len1 > 30 ? 30 : len1); + + len1 = strlen(line); + if (len1 >= 30) { + memset(&line[29], 0, len1 - 29); + } else { + for (i = 0; i < 30 - len1 - 1; i++) + strcat(line, " "); + } + + if (e->mode & SILC_UMODE_GONE) + strcat(line, " G"); + else + strcat(line, " H"); + strcat(tmp, m ? m : ""); + strncat(line, tmp, strlen(tmp)); + + if (strlen(tmp) < 5) + for (i = 0; i < 5 - strlen(tmp); i++) + strcat(line, " "); + + strcat(line, e->username ? e->username : ""); + + silc_say(client, conn, "%s", line); + + if (m) + silc_free(m); + } + + silc_free(line); + } + break; + + case SILC_COMMAND_BAN: + { + SilcChannelEntry channel; + char *ban_list; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + ban_list = va_arg(vp, char *); + + if (ban_list) + silc_say(client, conn, "%s ban list: %s", channel->channel_name, + ban_list); + else + silc_say(client, conn, "%s ban list not set", channel->channel_name); + } + break; + + case SILC_COMMAND_GETKEY: + { + SilcIdType id_type; + void *entry; + SilcPublicKey public_key; + unsigned char *pk; + uint32 pk_len; + + id_type = va_arg(vp, uint32); + entry = va_arg(vp, void *); + public_key = va_arg(vp, SilcPublicKey); + + pk = silc_pkcs_public_key_encode(public_key, &pk_len); + + if (id_type == SILC_ID_CLIENT) { + silc_verify_public_key_internal(client, conn, + SILC_SOCKET_TYPE_CLIENT, + pk, pk_len, SILC_SKE_PK_TYPE_SILC); + } + + silc_free(pk); + } + + case SILC_COMMAND_TOPIC: + { + SilcChannelEntry channel; + char *topic; + + if (!success) + return; + + channel = va_arg(vp, SilcChannelEntry); + topic = va_arg(vp, char *); + + if (topic) + silc_say(client, conn, + "Topic on channel %s: %s", channel->channel_name, + topic); + } + break; + + default: + break; + } +} + +/* Called to indicate that connection was either successfully established + or connecting failed. This is also the first time application receives + the SilcClientConnection objecet which it should save somewhere. */ + +void silc_connect(SilcClient client, SilcClientConnection conn, int success) +{ + SilcClientInternal app = (SilcClientInternal)client->application; + + if (success) { + app->screen->bottom_line->connection = conn->remote_host; + silc_screen_print_bottom_line(app->screen, 0); + app->conn = conn; + } +} + +/* Called to indicate that connection was disconnected to the server. */ + +void silc_disconnect(SilcClient client, SilcClientConnection conn) +{ + SilcClientInternal app = (SilcClientInternal)client->application; + + app->screen->bottom_line->connection = NULL; + silc_screen_print_bottom_line(app->screen, 0); + app->conn = NULL; +} + +/* Asks passphrase from user on the input line. */ + +void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context) +{ + SilcClientInternal app = (SilcClientInternal)conn->client->application; + char pass1[256], pass2[256]; + int try = 3; + + while(try) { + + /* Print prompt */ + wattroff(app->screen->input_win, A_INVIS); + silc_screen_input_print_prompt(app->screen, "Passphrase: "); + wattron(app->screen->input_win, A_INVIS); + + /* Get string */ + memset(pass1, 0, sizeof(pass1)); + wgetnstr(app->screen->input_win, pass1, sizeof(pass1)); + + /* Print retype prompt */ + wattroff(app->screen->input_win, A_INVIS); + silc_screen_input_print_prompt(app->screen, "Retype passphrase: "); + wattron(app->screen->input_win, A_INVIS); + + /* Get string */ + memset(pass2, 0, sizeof(pass2)); + wgetnstr(app->screen->input_win, pass2, sizeof(pass2)); + + if (!strncmp(pass1, pass2, strlen(pass2))) + break; + + try--; + } + + wattroff(app->screen->input_win, A_INVIS); + silc_screen_input_reset(app->screen); + + /* Deliver the passphrase to the library */ + completion(pass1, strlen(pass1), context); + + memset(pass1, 0, sizeof(pass1)); + memset(pass2, 0, sizeof(pass2)); +} + +/* Verifies received public key. The `conn_type' indicates which entity + (server, client etc.) has sent the public key. If user decides to trust + the key may be saved as trusted public key for later use. The + `completion' must be called after the public key has been verified. */ + +void silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context) +{ + if (silc_verify_public_key_internal(client, conn, conn_type, pk, + pk_len, pk_type)) { + completion(TRUE, context); + return; + } + + completion(FALSE, context); +} + +/* Find authentication method and authentication data by hostname and + port. The hostname may be IP address as well. The found authentication + method and authentication data is returned to `auth_meth', `auth_data' + and `auth_data_len'. The function returns TRUE if authentication method + is found and FALSE if not. `conn' may be NULL. */ + +int silc_get_auth_method(SilcClient client, SilcClientConnection conn, + char *hostname, uint16 port, + SilcProtocolAuthMeth *auth_meth, + unsigned char **auth_data, + uint32 *auth_data_len) +{ + SilcClientInternal app = (SilcClientInternal)client->application; + + if (app->config && app->config->conns) { + SilcClientConfigSectionConnection *conn = NULL; + + /* Check if we find a match from user configured connections */ + conn = silc_client_config_find_connection(app->config, + hostname, + port); + if (conn) { + /* Match found. Use the configured authentication method */ + *auth_meth = conn->auth_meth; + + if (conn->auth_data) { + *auth_data = strdup(conn->auth_data); + *auth_data_len = strlen(conn->auth_data); + } + + return TRUE; + } + } + + *auth_meth = SILC_AUTH_NONE; + *auth_data = NULL; + *auth_data_len = 0; + + return TRUE; +} + +/* Notifies application that failure packet was received. This is called + if there is some protocol active in the client. The `protocol' is the + protocol context. The `failure' is opaque pointer to the failure + indication. Note, that the `failure' is protocol dependant and application + must explicitly cast it to correct type. Usually `failure' is 32 bit + failure type (see protocol specs for all protocol failure types). */ + +void silc_failure(SilcClient client, SilcClientConnection conn, + SilcProtocol protocol, void *failure) +{ + if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) { + SilcSKEStatus status = (SilcSKEStatus)failure; + + if (status == SILC_SKE_STATUS_BAD_VERSION) + silc_say(client, conn, + "You are running incompatible client version (it may be " + "too old or too new)"); + if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY) + silc_say(client, conn, "Server does not support your public key type"); + if (status == SILC_SKE_STATUS_UNKNOWN_GROUP) + silc_say(client, conn, + "Server does not support one of your proposed KE group"); + if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER) + silc_say(client, conn, + "Server does not support one of your proposed cipher"); + if (status == SILC_SKE_STATUS_UNKNOWN_PKCS) + silc_say(client, conn, + "Server does not support one of your proposed PKCS"); + if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION) + silc_say(client, conn, + "Server does not support one of your proposed hash function"); + if (status == SILC_SKE_STATUS_UNKNOWN_HMAC) + silc_say(client, conn, + "Server does not support one of your proposed HMAC"); + if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE) + silc_say(client, conn, "Incorrect signature"); + } + + if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) { + uint32 err = (uint32)failure; + + if (err == SILC_AUTH_FAILED) + silc_say(client, conn, "Authentication failed"); + } +} + +/* Asks whether the user would like to perform the key agreement protocol. + This is called after we have received an key agreement packet or an + reply to our key agreement packet. This returns TRUE if the user wants + the library to perform the key agreement protocol and FALSE if it is not + desired (application may start it later by calling the function + silc_client_perform_key_agreement). */ + +int silc_key_agreement(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, char *hostname, + int port, + SilcKeyAgreementCallback *completion, + void **context) +{ + char host[256]; + + /* We will just display the info on the screen and return FALSE and user + will have to start the key agreement with a command. */ + + if (hostname) { + memset(host, 0, sizeof(host)); + snprintf(host, sizeof(host) - 1, "(%s on port %d)", hostname, port); + } + + silc_say(client, conn, "%s wants to perform key agreement %s", + client_entry->nickname, hostname ? host : ""); + + *completion = NULL; + *context = NULL; + + return FALSE; +} + +/* SILC client operations */ +SilcClientOperations ops = { + silc_op_say, + silc_channel_message, + silc_private_message, + silc_notify, + silc_command, + silc_command_reply, + silc_connect, + silc_disconnect, + silc_get_auth_method, + silc_verify_public_key, + silc_ask_passphrase, + silc_failure, + silc_key_agreement, +}; diff --git a/apps/silc/client_ops.h b/apps/silc/client_ops.h new file mode 100644 index 00000000..eb084caf --- /dev/null +++ b/apps/silc/client_ops.h @@ -0,0 +1,62 @@ +/* + + client_ops.h + + Author: Pekka Riikonen + + Copyright (C) 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. + +*/ + +#ifndef CLIENT_OPS_H +#define CLIENT_OPS_H + +void silc_say(SilcClient client, SilcClientConnection conn, char *msg, ...); +void silc_op_say(SilcClient client, SilcClientConnection conn, + SilcClientMessageType type, char *msg, ...); +void silc_channel_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, + SilcChannelEntry channel, + SilcMessageFlags flags, char *msg); +void silc_private_message(SilcClient client, SilcClientConnection conn, + SilcClientEntry sender, + SilcMessageFlags flags, char *msg); +void silc_notify(SilcClient client, SilcClientConnection conn, + SilcNotifyType type, ...); +void silc_command(SilcClient client, SilcClientConnection conn, + SilcClientCommandContext cmd_context, int success, + SilcCommand command); +void silc_command_reply(SilcClient client, SilcClientConnection conn, + SilcCommandPayload cmd_payload, int success, + SilcCommand command, SilcCommandStatus status, ...); +void silc_connect(SilcClient client, SilcClientConnection conn, int success); +void silc_disconnect(SilcClient client, SilcClientConnection conn); +void silc_ask_passphrase(SilcClient client, SilcClientConnection conn, + SilcAskPassphrase completion, void *context); +void silc_verify_public_key(SilcClient client, SilcClientConnection conn, + SilcSocketType conn_type, unsigned char *pk, + uint32 pk_len, SilcSKEPKType pk_type, + SilcVerifyPublicKey completion, void *context); +int silc_get_auth_method(SilcClient client, SilcClientConnection conn, + char *hostname, uint16 port, + SilcProtocolAuthMeth *auth_meth, + unsigned char **auth_data, + uint32 *auth_data_len); +void silc_failure(SilcClient client, SilcClientConnection conn, + SilcProtocol protocol, void *failure); +int silc_key_agreement(SilcClient client, SilcClientConnection conn, + SilcClientEntry client_entry, char *hostname, + int port, + SilcKeyAgreementCallback *completion, + void **context); +#endif diff --git a/apps/silc/clientconfig.c b/apps/silc/clientconfig.c index e55157b3..2fc31faf 100644 --- a/apps/silc/clientconfig.c +++ b/apps/silc/clientconfig.c @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -17,14 +17,7 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "clientincludes.h" #include "clientconfig.h" @@ -36,9 +29,11 @@ SilcClientConfigSection silc_client_config_sections[] = { { "[cipher]", SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER, 4 }, { "[pkcs]", - SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS, 2 }, + SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS, 1 }, { "[hash]", SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION, 4 }, + { "[hmac]", + SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC, 3 }, { "[connection]", SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION, 4 }, { "[commands]", @@ -60,11 +55,6 @@ SilcClientConfig silc_client_config_alloc(char *filename) SILC_LOG_DEBUG(("Allocating new configuration object")); new = silc_calloc(1, sizeof(*new)); - if (!new) { - fprintf(stderr, "Could not allocate new configuration object"); - return NULL; - } - new->filename = filename; /* Open configuration file and parse it */ @@ -107,7 +97,7 @@ int silc_client_config_parse(SilcClientConfig config, SilcBuffer buffer, SilcClientConfigParse *return_config) { int i, begin; - unsigned int linenum; + int linenum; char line[1024], *cp; SilcClientConfigSection *cptr = NULL; SilcClientConfigParse parse = *return_config, first = NULL; @@ -155,7 +145,7 @@ int silc_client_config_parse(SilcClientConfig config, SilcBuffer buffer, /* Check for matching sections */ for (cptr = silc_client_config_sections; cptr->section; cptr++) - if (!strcmp(cp, cptr->section)) + if (!strncasecmp(cp, cptr->section, strlen(cptr->section))) break; if (!cptr->section) { @@ -284,28 +274,28 @@ int silc_client_config_parse_lines(SilcClientConfig config, if (ret < 0) break; - /* Get block length */ + /* Get key length */ ret = silc_config_get_token(line, &tmp); if (ret < 0) break; if (ret == 0) { - fprintf(stderr, "%s:%d: Cipher block length not defined\n", + fprintf(stderr, "%s:%d: Cipher key length not defined\n", config->filename, pc->linenum); break; } - config->cipher->block_len = atoi(tmp); + config->cipher->key_len = atoi(tmp); silc_free(tmp); - /* Get key length */ + /* Get block length */ ret = silc_config_get_token(line, &tmp); if (ret < 0) break; if (ret == 0) { - fprintf(stderr, "%s:%d: Cipher key length not defined\n", + fprintf(stderr, "%s:%d: Cipher block length not defined\n", config->filename, pc->linenum); break; } - config->cipher->key_len = atoi(tmp); + config->cipher->block_len = atoi(tmp); silc_free(tmp); check = TRUE; @@ -337,18 +327,6 @@ int silc_client_config_parse_lines(SilcClientConfig config, break; } - /* Get key length */ - ret = silc_config_get_token(line, &tmp); - if (ret < 0) - break; - if (ret == 0) { - fprintf(stderr, "%s:%d: PKCS key length not defined\n", - config->filename, pc->linenum); - break; - } - config->pkcs->key_len = atoi(tmp); - silc_free(tmp); - check = TRUE; break; @@ -411,6 +389,57 @@ int silc_client_config_parse_lines(SilcClientConfig config, check = TRUE; break; + case SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC: + + if (!config->hmac) { + config->hmac = silc_calloc(1, sizeof(*config->hmac)); + config->hmac->next = NULL; + config->hmac->prev = NULL; + } else { + if (!config->hmac->next) { + config->hmac->next = + silc_calloc(1, sizeof(*config->hmac->next)); + config->hmac->next->next = NULL; + config->hmac->next->prev = config->hmac; + config->hmac = config->hmac->next; + } + } + + /* Get HMAC name */ + ret = silc_config_get_token(line, &config->hmac->alg_name); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: HMAC name not defined\n", + config->filename, pc->linenum); + break; + } + + /* Get Hash function name */ + ret = silc_config_get_token(line, &config->hmac->sim_name); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: Hash function name not defined\n", + config->filename, pc->linenum); + break; + } + + /* Get MAC length */ + ret = silc_config_get_token(line, &tmp); + if (ret < 0) + break; + if (ret == 0) { + fprintf(stderr, "%s:%d: HMAC's MAC length not defined\n", + config->filename, pc->linenum); + break; + } + config->hmac->key_len = atoi(tmp); + silc_free(tmp); + + check = TRUE; + break; + case SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION: if (!config->conns) { @@ -447,10 +476,10 @@ int silc_client_config_parse_lines(SilcClientConfig config, } if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PASSWD)) - config->conns->auth_meth = SILC_PROTOCOL_CONN_AUTH_PASSWORD; + config->conns->auth_meth = SILC_AUTH_PASSWORD; if (!strcmp(tmp, SILC_CLIENT_CONFIG_AUTH_METH_PUBKEY)) - config->conns->auth_meth = SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY; + config->conns->auth_meth = SILC_AUTH_PUBLIC_KEY; silc_free(tmp); } @@ -490,7 +519,9 @@ int silc_client_config_parse_lines(SilcClientConfig config, /* Get command line (this may include parameters as well. They will be parsed later with standard command parser when executing particular command.) */ - config->commands->command = strdup(line->data); + config->commands->command = silc_calloc(strlen(line->data), + sizeof(char)); + memcpy(config->commands->command, line->data, strlen(line->data) - 1); if (ret < 0) break; @@ -523,6 +554,8 @@ int silc_client_config_parse_lines(SilcClientConfig config, config->pkcs = config->pkcs->prev; while (config->hash_func && config->hash_func->prev) config->hash_func = config->hash_func->prev; + while (config->hmac && config->hmac->prev) + config->hmac = config->hmac->prev; while (config->conns && config->conns->prev) config->conns = config->conns->prev; while (config->commands && config->commands->prev) @@ -536,33 +569,43 @@ int silc_client_config_parse_lines(SilcClientConfig config, /* Registers configured ciphers. These can then be allocated by the client when needed. */ -void silc_client_config_register_ciphers(SilcClientConfig config) +bool silc_client_config_register_ciphers(SilcClientConfig config) { SilcClientConfigSectionAlg *alg; - SilcClient client = (SilcClient)config->client; + SilcClientInternal app = (SilcClientInternal)config->client; + SilcClient client = app->client; SILC_LOG_DEBUG(("Registering configured ciphers")); + if (!config->cipher) + return FALSE; + alg = config->cipher; while(alg) { if (!alg->sim_name) { - /* Crypto module is supposed to be built in. Nothing to be done - here except to test that the cipher really is built in. */ - SilcCipher tmp = NULL; + /* Crypto module is supposed to be built in. Get the pointer to the + built in cipher and register it. */ + int i; + + for (i = 0; silc_default_ciphers[i].name; i++) + if (!strcmp(silc_default_ciphers[i].name, alg->alg_name)) { + silc_cipher_register(&silc_default_ciphers[i]); + break; + } - if (silc_cipher_alloc(alg->alg_name, &tmp) == FALSE) { - SILC_LOG_ERROR(("Unsupported cipher `%s'", alg->alg_name)); + if (!silc_cipher_is_supported(alg->alg_name)) { + SILC_LOG_ERROR(("Unknown cipher `%s'", alg->alg_name)); silc_client_stop(client); exit(1); } - silc_cipher_free(tmp); #ifdef SILC_SIM } else { /* Load (try at least) the crypto SIM module */ SilcCipherObject cipher; SilcSimContext *sim; + char *alg_name; memset(&cipher, 0, sizeof(cipher)); cipher.name = alg->alg_name; @@ -573,34 +616,40 @@ void silc_client_config_register_ciphers(SilcClientConfig config) sim->type = SILC_SIM_CIPHER; sim->libname = alg->sim_name; + alg_name = strdup(alg->alg_name); + if (strchr(alg_name, '-')) + *strchr(alg_name, '-') = '\0'; + if ((silc_sim_load(sim))) { cipher.set_key = - silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + silc_sim_getsym(sim, silc_sim_symname(alg_name, SILC_CIPHER_SIM_SET_KEY)); SILC_LOG_DEBUG(("set_key=%p", cipher.set_key)); cipher.set_key_with_string = - silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, - SILC_CIPHER_SIM_SET_KEY_WITH_STRING)); + silc_sim_getsym(sim, silc_sim_symname(alg_name, + SILC_CIPHER_SIM_SET_KEY_WITH_STRING)); SILC_LOG_DEBUG(("set_key_with_string=%p", cipher.set_key_with_string)); cipher.encrypt = - silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + silc_sim_getsym(sim, silc_sim_symname(alg_name, SILC_CIPHER_SIM_ENCRYPT_CBC)); SILC_LOG_DEBUG(("encrypt_cbc=%p", cipher.encrypt)); cipher.decrypt = - silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + silc_sim_getsym(sim, silc_sim_symname(alg_name, SILC_CIPHER_SIM_DECRYPT_CBC)); SILC_LOG_DEBUG(("decrypt_cbc=%p", cipher.decrypt)); cipher.context_len = - silc_sim_getsym(sim, silc_sim_symname(alg->alg_name, + silc_sim_getsym(sim, silc_sim_symname(alg_name, SILC_CIPHER_SIM_CONTEXT_LEN)); SILC_LOG_DEBUG(("context_len=%p", cipher.context_len)); /* Put the SIM to the table of all SIM's in client */ - client->sim = silc_realloc(client->sim, - sizeof(*client->sim) * - (client->sim_count + 1)); - client->sim[client->sim_count] = sim; - client->sim_count++; + app->sim = silc_realloc(app->sim, + sizeof(*app->sim) * + (app->sim_count + 1)); + app->sim[app->sim_count] = sim; + app->sim_count++; + + silc_free(alg_name); } else { SILC_LOG_ERROR(("Error configuring ciphers")); silc_client_stop(client); @@ -614,60 +663,74 @@ void silc_client_config_register_ciphers(SilcClientConfig config) alg = alg->next; } + + return TRUE; } /* Registers configured PKCS's. */ -/* XXX: This really doesn't do anything now since we have statically - registered our PKCS's. This should be implemented when PKCS works - as SIM's. This checks now only that the PKCS user requested is - really out there. */ -void silc_client_config_register_pkcs(SilcClientConfig config) +bool silc_client_config_register_pkcs(SilcClientConfig config) { SilcClientConfigSectionAlg *alg = config->pkcs; - SilcClient client = (SilcClient)config->client; - SilcPKCS tmp = NULL; + SilcClientInternal app = (SilcClientInternal)config->client; + SilcClient client = app->client; SILC_LOG_DEBUG(("Registering configured PKCS")); - while(alg) { + if (!alg) + return FALSE; - if (silc_pkcs_alloc(alg->alg_name, &tmp) == FALSE) { - SILC_LOG_ERROR(("Unsupported PKCS `%s'", alg->alg_name)); + while(alg) { + int i; + + for (i = 0; silc_default_pkcs[i].name; i++) + if (!strcmp(silc_default_pkcs[i].name, alg->alg_name)) { + silc_pkcs_register(&silc_default_pkcs[i]); + break; + } + + if (!silc_pkcs_is_supported(alg->alg_name)) { + SILC_LOG_ERROR(("Unknown PKCS `%s'", alg->alg_name)); silc_client_stop(client); exit(1); } - silc_free(tmp); alg = alg->next; } + + return TRUE; } -/* Registers configured hash functions. These can then be allocated by the +/* Registers configured hash funtions. These can then be allocated by the client when needed. */ -void silc_client_config_register_hashfuncs(SilcClientConfig config) +bool silc_client_config_register_hashfuncs(SilcClientConfig config) { SilcClientConfigSectionAlg *alg; - SilcClient client = (SilcClient)config->client; + SilcClientInternal app = (SilcClientInternal)config->client; + SilcClient client = app->client; SILC_LOG_DEBUG(("Registering configured hash functions")); + if (!config->hash_func) + return FALSE; + alg = config->hash_func; while(alg) { - if (!alg->sim_name) { - /* Hash module is supposed to be built in. Nothing to be done - here except to test that the hash function really is built in. */ - SilcHash tmp = NULL; - - if (silc_hash_alloc(alg->alg_name, &tmp) == FALSE) { - SILC_LOG_ERROR(("Unsupported hash function `%s'", alg->alg_name)); + int i; + + for (i = 0; silc_default_hash[i].name; i++) + if (!strcmp(silc_default_hash[i].name, alg->alg_name)) { + silc_hash_register(&silc_default_hash[i]); + break; + } + + if (!silc_hash_is_supported(alg->alg_name)) { + SILC_LOG_ERROR(("Unknown hash function `%s'", alg->alg_name)); silc_client_stop(client); exit(1); } - silc_free(tmp); - #ifdef SILC_SIM } else { /* Load (try at least) the hash SIM module */ @@ -702,26 +765,63 @@ void silc_client_config_register_hashfuncs(SilcClientConfig config) SILC_LOG_DEBUG(("context_len=%p", hash.context_len)); /* Put the SIM to the table of all SIM's in client */ - client->sim = silc_realloc(client->sim, - sizeof(*client->sim) * - (client->sim_count + 1)); - client->sim[client->sim_count] = sim; - client->sim_count++; + app->sim = silc_realloc(app->sim, + sizeof(*app->sim) * + (app->sim_count + 1)); + app->sim[app->sim_count] = sim; + app->sim_count++; } else { SILC_LOG_ERROR(("Error configuring hash functions")); silc_client_stop(client); exit(1); } - /* Register the cipher */ + /* Register the hash function */ silc_hash_register(&hash); #endif } - alg = alg->next; } + + return TRUE; } +/* Registers configured HMACs. These can then be allocated by the + client when needed. */ + +bool silc_client_config_register_hmacs(SilcClientConfig config) +{ + SilcClientConfigSectionAlg *alg; + SilcClientInternal app = (SilcClientInternal)config->client; + SilcClient client = app->client; + + SILC_LOG_DEBUG(("Registering configured HMACs")); + + if (!config->hmac) + return FALSE; + + alg = config->hmac; + while(alg) { + SilcHmacObject hmac; + + if (!silc_hash_is_supported(alg->sim_name)) { + SILC_LOG_ERROR(("Unknown hash function `%s' for HMAC `%s'", + alg->sim_name, alg->alg_name)); + silc_client_stop(client); + exit(1); + } + + /* Register the HMAC */ + memset(&hmac, 0, sizeof(hmac)); + hmac.name = alg->alg_name; + hmac.len = alg->key_len; + silc_hmac_register(&hmac); + + alg = alg->next; + } + + return TRUE; +} SilcClientConfigSectionConnection * silc_client_config_find_connection(SilcClientConfig config, diff --git a/apps/silc/clientconfig.h b/apps/silc/clientconfig.h index f2c7a4af..7644c030 100644 --- a/apps/silc/clientconfig.h +++ b/apps/silc/clientconfig.h @@ -25,8 +25,8 @@ typedef struct SilcClientConfigSectionAlgStruct { char *alg_name; char *sim_name; - unsigned int block_len; - unsigned int key_len; + uint32 block_len; + uint32 key_len; struct SilcClientConfigSectionAlgStruct *next; struct SilcClientConfigSectionAlgStruct *prev; #define SILC_CLIENT_CONFIG_MODNAME "builtin" @@ -37,7 +37,7 @@ typedef struct SilcClientConfigSectionConnectionStruct { char *host; int auth_meth; char *auth_data; - unsigned short port; + uint16 port; struct SilcClientConfigSectionConnectionStruct *next; struct SilcClientConfigSectionConnectionStruct *prev; #define SILC_CLIENT_CONFIG_AUTH_METH_PASSWD "passwd" @@ -69,6 +69,7 @@ typedef struct { SilcClientConfigSectionAlg *cipher; SilcClientConfigSectionAlg *pkcs; SilcClientConfigSectionAlg *hash_func; + SilcClientConfigSectionAlg *hmac; SilcClientConfigSectionConnection *conns; SilcClientConfigSectionCommand *commands; } SilcClientConfigObject; @@ -81,6 +82,7 @@ typedef enum { SILC_CLIENT_CONFIG_SECTION_TYPE_CIPHER, SILC_CLIENT_CONFIG_SECTION_TYPE_PKCS, SILC_CLIENT_CONFIG_SECTION_TYPE_HASH_FUNCTION, + SILC_CLIENT_CONFIG_SECTION_TYPE_HMAC, SILC_CLIENT_CONFIG_SECTION_TYPE_CONNECTION, SILC_CLIENT_CONFIG_SECTION_TYPE_COMMAND = 253, /* Special section */ } SilcClientConfigSectionType; @@ -89,7 +91,7 @@ typedef enum { typedef struct { const char *section; SilcClientConfigSectionType type; - unsigned int maxfields; + int maxfields; } SilcClientConfigSection; /* List of all possible config sections in SILC client */ @@ -99,7 +101,7 @@ extern SilcClientConfigSection silc_client_config_sections[]; from a file to this structure before parsing it further. */ typedef struct SilcClientConfigParseStruct { SilcBuffer line; - unsigned int linenum; + int linenum; SilcClientConfigSection *section; struct SilcClientConfigParseStruct *next; struct SilcClientConfigParseStruct *prev; @@ -112,11 +114,12 @@ int silc_client_config_parse(SilcClientConfig config, SilcBuffer buffer, SilcClientConfigParse *return_config); int silc_client_config_parse_lines(SilcClientConfig config, SilcClientConfigParse parse_config); -int silc_client_config_check_sections(unsigned int checkmask); +int silc_client_config_check_sections(uint32 checkmask); void silc_client_config_setlogfiles(SilcClientConfig config); -void silc_client_config_register_ciphers(SilcClientConfig config); -void silc_client_config_register_pkcs(SilcClientConfig config); -void silc_client_config_register_hashfuncs(SilcClientConfig config); +bool silc_client_config_register_ciphers(SilcClientConfig config); +bool silc_client_config_register_pkcs(SilcClientConfig config); +bool silc_client_config_register_hashfuncs(SilcClientConfig config); +bool silc_client_config_register_hmacs(SilcClientConfig config); SilcClientConfigSectionConnection * silc_client_config_find_connection(SilcClientConfig config, char *host, int port); diff --git a/includes/clientincludes.h b/apps/silc/clientincludes.h similarity index 80% rename from includes/clientincludes.h rename to apps/silc/clientincludes.h index 479c0007..f6b56f14 100644 --- a/includes/clientincludes.h +++ b/apps/silc/clientincludes.h @@ -21,23 +21,29 @@ #ifndef CLIENTINCLUDES_H #define CLIENTINCLUDES_H -#include -#include -#include -#include +#include "silcdefs.h" /* Generic includes */ #include "silcincludes.h" +#include "clientlibincludes.h" + +#if defined(USE_NCURSES) && !defined(RENAMED_NCURSES) +#include +#else +#include +#endif +#include + +#ifdef HAVE_PATHS_H +#include +#endif /* SILC Client includes */ -#include "idlist.h" #include "screen.h" #include "clientconfig.h" -#include "client.h" +#include "local_command.h" #include "clientutil.h" -#include "protocol.h" -#include "command.h" -#include "command_reply.h" #include "silc.h" +#include "client_ops.h" #endif diff --git a/apps/silc/clientutil.c b/apps/silc/clientutil.c index 21043795..1bcc9c8b 100644 --- a/apps/silc/clientutil.c +++ b/apps/silc/clientutil.c @@ -17,21 +17,14 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "clientincludes.h" -/* Internal routine used to print lines to window. This can split the +/* Routine used to print lines to window. This can split the line neatly if a word would overlap the line. */ -static void silc_print_to_window(WINDOW *win, char *message) +void silc_print_to_window(WINDOW *win, char *message) { int str_len, len; @@ -62,36 +55,14 @@ static void silc_print_to_window(WINDOW *win, char *message) wrefresh(win); } -/* Prints a message with three star (*) sign before the actual message - on the current output window. This is used to print command outputs - and error messages. */ -/* XXX Change to accept SilcClientWindow and use output window - from there (the pointer to the output window must be added to the - SilcClientWindow object. */ - -void silc_say(SilcClient client, char *msg, ...) -{ - va_list vp; - char message[1024]; - - memset(message, 0, sizeof(message)); - strncat(message, "\n*** ", 5); - - va_start(vp, msg); - vsprintf(message + 5, msg, vp); - va_end(vp); - - /* Print the message */ - silc_print_to_window(client->screen->output_win[0], message); -} - /* Prints message to the screen. This is used to print the messages user is typed and message that came on channels. */ void silc_print(SilcClient client, char *msg, ...) { va_list vp; - char message[1024]; + char message[2048]; + SilcClientInternal app = client->application; memset(message, 0, sizeof(message)); strncat(message, "\n ", 2); @@ -101,7 +72,7 @@ void silc_print(SilcClient client, char *msg, ...) va_end(vp); /* Print the message */ - silc_print_to_window(client->screen->output_win[0], message); + silc_print_to_window(app->screen->output_win[0], message); } /* Returns user's mail path */ @@ -110,8 +81,13 @@ char *silc_get_mail_path() { char pathbuf[MAXPATHLEN]; char *path; + +#ifndef _PATH_MAILDIR +#define _PATH_MAILDIR "/var/mail" +#endif - if ((path = (char *)getenv("MAIL")) != 0) { + path = getenv("MAIL"); + if (path) { strncpy(pathbuf, path, strlen(path)); } else { strcpy(pathbuf, _PATH_MAILDIR); @@ -138,7 +114,7 @@ int silc_get_number_of_emails() fprintf(stderr, "Couldn't open mail file (%s).\n", filename); } else { while((fscanf(tl, "%s", data)) != EOF) { - if(!strcmp(data, "Subject:")) + if(!strcmp(data, "From:")) num++; } @@ -148,53 +124,6 @@ int silc_get_number_of_emails() return num; } -/* Returns the username of the user. If the global variable LOGNAME - does not exists we will get the name from the password file. */ - -char *silc_get_username() -{ - char *logname = NULL; - - logname = strdup(getenv("LOGNAME")); - if (!logname) { - logname = getlogin(); - if (!logname) { - struct passwd *pw; - - pw = getpwuid(getuid()); - if (!pw) { - fprintf(stderr, "silc_get_username: %s\n", strerror(errno)); - return NULL; - } - - logname = strdup(pw->pw_name); - } - } - - return logname; -} - -/* Returns the real name of ther user. */ - -char *silc_get_real_name() -{ - char *realname = NULL; - struct passwd *pw; - - pw = getpwuid(getuid()); - if (!pw) { - fprintf(stderr, "silc_get_username: %s\n", strerror(errno)); - return NULL; - } - - if (strchr(pw->pw_gecos, ',')) - *strchr(pw->pw_gecos, ',') = 0; - - realname = strdup(pw->pw_gecos); - - return realname; -} - /* Returns time til next minute changes. Used to update the clock when needed. */ @@ -209,48 +138,39 @@ int silc_client_time_til_next_min() return 60 - min->tm_sec; } -/* Asks passphrase from user on the input line. */ +/* Asks yes/no from user on the input line. Returns TRUE on "yes" and + FALSE on "no". */ -char *silc_client_ask_passphrase(SilcClient client) +int silc_client_ask_yes_no(SilcClient client, char *prompt) { - char pass1[256], pass2[256]; - char *ret; - int try = 3; - - while(try) { - - /* Print prompt */ - wattroff(client->screen->input_win, A_INVIS); - silc_screen_input_print_prompt(client->screen, "Passphrase: "); - wattron(client->screen->input_win, A_INVIS); - - /* Get string */ - memset(pass1, 0, sizeof(pass1)); - wgetnstr(client->screen->input_win, pass1, sizeof(pass1)); - - /* Print retype prompt */ - wattroff(client->screen->input_win, A_INVIS); - silc_screen_input_print_prompt(client->screen, "Retype passphrase: "); - wattron(client->screen->input_win, A_INVIS); - - /* Get string */ - memset(pass2, 0, sizeof(pass2)); - wgetnstr(client->screen->input_win, pass2, sizeof(pass2)); - - if (!strncmp(pass1, pass2, strlen(pass2))) - break; - - try--; + SilcClientInternal app = (SilcClientInternal)client->application; + char answer[4]; + int ret; + + again: + silc_screen_input_reset(app->screen); + + /* Print prompt */ + wattroff(app->screen->input_win, A_INVIS); + silc_screen_input_print_prompt(app->screen, prompt); + + /* Get string */ + memset(answer, 0, sizeof(answer)); + echo(); + wgetnstr(app->screen->input_win, answer, sizeof(answer)); + if (!strncasecmp(answer, "yes", strlen(answer)) || + !strncasecmp(answer, "y", strlen(answer))) { + ret = TRUE; + } else if (!strncasecmp(answer, "no", strlen(answer)) || + !strncasecmp(answer, "n", strlen(answer))) { + ret = FALSE; + } else { + silc_say(client, app->conn, "Type yes or no"); + goto again; } + noecho(); - ret = silc_calloc(strlen(pass1), sizeof(char)); - memcpy(ret, pass1, strlen(pass1)); - - memset(pass1, 0, sizeof(pass1)); - memset(pass2, 0, sizeof(pass2)); - - wattroff(client->screen->input_win, A_INVIS); - silc_screen_input_reset(client->screen); + silc_screen_input_reset(app->screen); return ret; } @@ -259,21 +179,27 @@ char *silc_client_ask_passphrase(SilcClient client) void silc_client_list_ciphers() { - + char *ciphers = silc_cipher_get_supported(); + fprintf(stdout, "%s\n", ciphers); + silc_free(ciphers); } /* Lists supported (builtin) hash functions */ void silc_client_list_hash_funcs() { - + char *hash = silc_hash_get_supported(); + fprintf(stdout, "%s\n", hash); + silc_free(hash); } /* Lists supported PKCS algorithms */ void silc_client_list_pkcs() { - + char *pkcs = silc_pkcs_get_supported(); + fprintf(stdout, "%s\n", pkcs); + silc_free(pkcs); } /* Displays input prompt on command line and takes input data from user */ @@ -286,7 +212,7 @@ char *silc_client_get_input(const char *prompt) fd = open("/dev/tty", O_RDONLY); if (fd < 0) { fprintf(stderr, "silc: %s\n", strerror(errno)); - exit(1); + return NULL; } memset(input, 0, sizeof(input)); @@ -296,7 +222,7 @@ char *silc_client_get_input(const char *prompt) if ((read(fd, input, sizeof(input))) < 0) { fprintf(stderr, "silc: %s\n", strerror(errno)); - exit(1); + return NULL; } if (strlen(input) <= 1) @@ -323,7 +249,7 @@ char *silc_client_get_passphrase(const char *prompt) fd = open("/dev/tty", O_RDONLY); if (fd < 0) { fprintf(stderr, "silc: %s\n", strerror(errno)); - exit(1); + return NULL; } signal(SIGINT, SIG_IGN); @@ -343,7 +269,7 @@ char *silc_client_get_passphrase(const char *prompt) if ((read(fd, input, sizeof(input))) < 0) { fprintf(stderr, "silc: %s\n", strerror(errno)); - exit(1); + return NULL; } if (strlen(input) <= 1) { @@ -367,18 +293,52 @@ char *silc_client_get_passphrase(const char *prompt) #endif } +/* Returns identifier string for public key generation. */ + +char *silc_client_create_identifier() +{ + char *username = NULL, *realname = NULL; + char hostname[256], email[256]; + + /* Get realname */ + realname = silc_get_real_name(); + + /* Get hostname */ + memset(hostname, 0, sizeof(hostname)); + gethostname(hostname, sizeof(hostname)); + + /* Get username (mandatory) */ + username = silc_get_username(); + if (!username) + return NULL; + + /* Create default email address, whether it is right or not */ + snprintf(email, sizeof(email), "%s@%s", username, hostname); + + return silc_pkcs_encode_identifier(username, hostname, realname, email, + NULL, NULL); +} + /* Creates new public key and private key pair. This is used only when user wants to create new key pair from command line. */ -void silc_client_create_key_pair(char *pkcs_name, int bits) +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) { SilcPKCS pkcs; + SilcPublicKey pub_key; + SilcPrivateKey prv_key; SilcRng rng; unsigned char *key; - unsigned int key_len; + uint32 key_len; + char line[256]; char *pkfile = NULL, *prvfile = NULL; - printf("\ + if (!pkcs_name || !public_key || !private_key) + printf("\ New pair of keys will be created. Please, answer to following questions.\n\ "); @@ -396,6 +356,11 @@ New pair of keys will be created. Please, answer to following questions.\n\ } } + if (!silc_pkcs_is_supported(pkcs_name)) { + fprintf(stderr, "Unknown PKCS `%s'", pkcs_name); + return FALSE; + } + if (!bits) { char *length = NULL; length = @@ -406,39 +371,401 @@ New pair of keys will be created. Please, answer to following questions.\n\ bits = atoi(length); } + if (!identifier) { + char *def = silc_client_create_identifier(); + + memset(line, 0, sizeof(line)); + if (def) + snprintf(line, sizeof(line), "Identifier [%s]: ", def); + else + snprintf(line, sizeof(line), + "Identifier (eg. UN=jon, HN=jon.dummy.com, " + "RN=Jon Johnson, E=jon@dummy.com): "); + + while (!identifier) { + identifier = silc_client_get_input(line); + if (!identifier && def) + identifier = strdup(def); + } + + if (def) + silc_free(def); + } + rng = silc_rng_alloc(); silc_rng_init(rng); - silc_math_primegen_init(); - - again_pk: - pkfile = silc_client_get_input("Public key filename: "); - if (!pkfile) { - printf("Public key filename must be defined\n"); - goto again_pk; + silc_rng_global_init(rng); + + if (!public_key) { + memset(line, 0, sizeof(line)); + snprintf(line, sizeof(line), "Public key filename [%s] ", + SILC_CLIENT_PUBLIC_KEY_NAME); + pkfile = silc_client_get_input(line); + if (!pkfile) + pkfile = SILC_CLIENT_PUBLIC_KEY_NAME; + } else { + pkfile = public_key; } - again_prv: - prvfile = silc_client_get_input("Private key filename: "); - if (!prvfile) { - printf("Private key filename must be defined\n"); - goto again_prv; + if (!private_key) { + memset(line, 0, sizeof(line)); + snprintf(line, sizeof(line), "Public key filename [%s] ", + SILC_CLIENT_PRIVATE_KEY_NAME); + prvfile = silc_client_get_input(line); + if (!prvfile) + prvfile = SILC_CLIENT_PRIVATE_KEY_NAME; + } else { + prvfile = private_key; } /* Generate keys */ silc_pkcs_alloc(pkcs_name, &pkcs); pkcs->pkcs->init(pkcs->context, bits, rng); - /* Save keys into file */ + /* Save public key into file */ key = silc_pkcs_get_public_key(pkcs, &key_len); - silc_pkcs_save_public_key(pkcs, pkfile, key, key_len); + pub_key = silc_pkcs_public_key_alloc(pkcs->pkcs->name, identifier, + key, key_len); + silc_pkcs_save_public_key(pkfile, pub_key, SILC_PKCS_FILE_PEM); + if (ret_pub_key) + *ret_pub_key = pub_key; + memset(key, 0, sizeof(key_len)); silc_free(key); + + /* Save private key into file */ key = silc_pkcs_get_private_key(pkcs, &key_len); - silc_pkcs_save_private_key(pkcs, prvfile, key, key_len, ""); + prv_key = silc_pkcs_private_key_alloc(pkcs->pkcs->name, key, key_len); + + silc_pkcs_save_private_key(prvfile, prv_key, NULL, SILC_PKCS_FILE_BIN); + if (ret_prv_key) + *ret_prv_key = prv_key; + + printf("Public key has been saved into `%s'.\n", pkfile); + printf("Private key has been saved into `%s'.\n", prvfile); + printf("Press to continue...\n"); + getchar(); + memset(key, 0, sizeof(key_len)); silc_free(key); - 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], clientfilename[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); + snprintf(clientfilename, sizeof(clientfilename) - 1, "%s/.silc/clientkeys", + 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 ~./silc/clientkeys directory + */ + if ((stat(clientfilename, &st)) == -1) { + /* If dir doesn't exist */ + if (errno == ENOENT) { + if (pw->pw_uid == geteuid()) { + if ((mkdir(clientfilename, 0755)) == -1) { + fprintf(stderr, "Couldn't create `%s' directory\n", clientfilename); + return FALSE; + } + } else { + fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n", + clientfilename); + 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; +} + +/* Dumps the public key on screen. Used from the command line option. */ + +int silc_client_show_key(char *keyfile) +{ + SilcPublicKey public_key; + SilcPublicKeyIdentifier ident; + char *fingerprint; + unsigned char *pk; + uint32 pk_len; + SilcPKCS pkcs; + int key_len = 0; + + if (silc_pkcs_load_public_key(keyfile, &public_key, + SILC_PKCS_FILE_PEM) == FALSE) + if (silc_pkcs_load_public_key(keyfile, &public_key, + SILC_PKCS_FILE_BIN) == FALSE) { + fprintf(stderr, "Could not load public key file `%s'\n", keyfile); + return FALSE; + } + + ident = silc_pkcs_decode_identifier(public_key->identifier); + + pk = silc_pkcs_public_key_encode(public_key, &pk_len); + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + + if (silc_pkcs_alloc(public_key->name, &pkcs)) { + key_len = silc_pkcs_public_key_set(pkcs, public_key); + silc_pkcs_free(pkcs); + } + + printf("Public key file : %s\n", keyfile); + printf("Algorithm : %s\n", public_key->name); + if (key_len) + printf("Key length (bits) : %d\n", key_len); + if (ident->realname) + printf("Real name : %s\n", ident->realname); + if (ident->username) + printf("Username : %s\n", ident->username); + if (ident->host) + printf("Hostname : %s\n", ident->host); + if (ident->email) + printf("Email : %s\n", ident->email); + if (ident->org) + printf("Organization : %s\n", ident->org); + if (ident->country) + printf("Country : %s\n", ident->country); + printf("Fingerprint (SHA1) : %s\n", fingerprint); + + fflush(stdout); + + silc_free(fingerprint); + silc_free(pk); + silc_pkcs_public_key_free(public_key); + silc_pkcs_free_identifier(ident); + + return TRUE; } diff --git a/apps/silc/clientutil.h b/apps/silc/clientutil.h index e18cf2d7..b4215198 100644 --- a/apps/silc/clientutil.h +++ b/apps/silc/clientutil.h @@ -22,19 +22,25 @@ #define CLIENTUTIL_H /* Prototypes */ -void silc_say(SilcClient client, char *msg, ...); +void silc_print_to_window(WINDOW *win, char *message); void silc_print(SilcClient client, char *msg, ...); char *silc_get_mail_path(); int silc_get_number_of_emails(); -char *silc_get_username(); -char *silc_get_real_name(); int silc_client_time_til_next_min(); -char *silc_client_ask_passphrase(SilcClient client); +int silc_client_ask_yes_no(SilcClient client, char *prompt); char *silc_client_get_input(const char *prompt); char *silc_client_get_passphrase(const char *prompt); 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); +int silc_client_show_key(char *keyfile); #endif diff --git a/apps/silc/command.c b/apps/silc/command.c deleted file mode 100644 index 96c8e3c3..00000000 --- a/apps/silc/command.c +++ /dev/null @@ -1,584 +0,0 @@ -/* - - command.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$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ - -#include "clientincludes.h" - -/* Client command list. */ -SilcClientCommand silc_command_list[] = -{ - SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", SILC_CF_LAG | SILC_CF_REG, 3), - SILC_CLIENT_CMD(whowas, WHOWAS, "WHOWAS", SILC_CF_LAG | SILC_CF_REG, 3), - SILC_CLIENT_CMD(identify, IDENTIFY, "IDENTIFY", - SILC_CF_LAG | SILC_CF_REG, 3), - SILC_CLIENT_CMD(nick, NICK, "NICK", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(list, LIST, "LIST", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(topic, TOPIC, "TOPIC", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(invite, INVITE, "INVITE", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(quit, QUIT, "QUIT", SILC_CF_LAG | SILC_CF_REG, 1), - SILC_CLIENT_CMD(kill, KILL, "KILL", - SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2), - SILC_CLIENT_CMD(info, INFO, "INFO", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(connect, CONNECT, "CONNECT", - SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2), - SILC_CLIENT_CMD(ping, PING, "PING", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(oper, OPER, "OPER", - SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2), - SILC_CLIENT_CMD(join, JOIN, "JOIN", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(motd, MOTD, "MOTD", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(umode, UMODE, "UMODE", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(cmode, CMODE, "CMODE", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(kick, KICK, "KICK", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(restart, RESTART, "RESTART", - SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2), - SILC_CLIENT_CMD(close, CLOSE, "CLOSE", - SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2), - SILC_CLIENT_CMD(die, DIE, "DIE", - SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2), - SILC_CLIENT_CMD(silcoper, SILCOPER, "SILOPER", - SILC_CF_LAG | SILC_CF_REG | SILC_CF_SILC_OPER, 2), - SILC_CLIENT_CMD(leave, LEAVE, "LEAVE", SILC_CF_LAG | SILC_CF_REG, 2), - SILC_CLIENT_CMD(names, NAMES, "NAMES", SILC_CF_LAG | SILC_CF_REG, 2), - - /* - * Local. client specific commands - */ - SILC_CLIENT_CMD(help, HELP, "HELP", SILC_CF_NONE, 2), - SILC_CLIENT_CMD(clear, CLEAR, "CLEAR", SILC_CF_NONE, 1), - SILC_CLIENT_CMD(version, VERSION, "VERSION", SILC_CF_NONE, 1), - SILC_CLIENT_CMD(server, SERVER, "SERVER", SILC_CF_NONE, 2), - SILC_CLIENT_CMD(msg, MSG, "MSG", SILC_CF_NONE, 3), - SILC_CLIENT_CMD(away, AWAY, "AWAY", SILC_CF_NONE, 2), - - { NULL, 0, NULL, 0}, -}; - -/* List of pending commands. */ -SilcClientCommandPending *silc_command_pending = NULL; - -/* Add new pending command to the list of pending commands. Currently - pending commands are executed from command replies, thus we can - execute any command after receiving some specific command reply. - - The argument `reply_cmd' is the command reply from where the callback - function is to be called, thus, it IS NOT the command to be executed. - - XXX: If needed in the future this support may be extended for - commands as well, when any command could be executed after executing - some specific command. */ - -void silc_client_command_pending(SilcCommand reply_cmd, - SilcClientCommandCallback callback, - void *context) -{ - SilcClientCommandPending *reply, *r; - - reply = silc_calloc(1, sizeof(*reply)); - reply->reply_cmd = reply_cmd; - reply->context = context; - reply->callback = callback; - - if (silc_command_pending == NULL) { - silc_command_pending = reply; - return; - } - - for (r = silc_command_pending; r; r = r->next) { - if (r->next == NULL) { - r->next = reply; - break; - } - } -} - -/* Deletes pending command by reply command type. */ - -void silc_client_command_pending_del(SilcCommand reply_cmd) -{ - SilcClientCommandPending *r, *tmp; - - if (silc_command_pending) { - if (silc_command_pending->reply_cmd == reply_cmd) { - silc_free(silc_command_pending); - silc_command_pending = NULL; - return; - } - - for (r = silc_command_pending; r; r = r->next) { - if (r->next && r->next->reply_cmd == reply_cmd) { - tmp = r->next; - r->next = r->next->next; - silc_free(tmp); - break; - } - } - } -} - -/* Free command context and its internals */ - -static void silc_client_command_free(SilcClientCommandContext cmd) -{ - int i; - - if (cmd) { - for (i = 0; i < cmd->argc; i++) - silc_free(cmd->argv[i]); - silc_free(cmd); - } -} - -/* Command WHOIS. This command is used to query information about - specific user. */ - -SILC_CLIENT_CMD_FUNC(whois) -{ - SilcClientCommandContext cmd = (SilcClientCommandContext)context; - SilcBuffer buffer; - - if (cmd->argc < 2 || cmd->argc > 3) { - silc_say(cmd->client, "Usage: /WHOIS [@] []"); - goto out; - } - - if (!cmd->client->current_win->sock) { - silc_say(cmd->client, - "You are not connected to a server, use /SERVER to connect"); - goto out; - } - - buffer = silc_command_encode_payload(SILC_COMMAND_WHOIS, - cmd->argc - 1, ++cmd->argv, - ++cmd->argv_lens, ++cmd->argv_types); - silc_client_packet_send(cmd->client, cmd->client->current_win->sock, - SILC_PACKET_COMMAND, NULL, 0, NULL, NULL, - buffer->data, buffer->len, TRUE); - silc_buffer_free(buffer); - cmd->argv--; - cmd->argv_lens--; - cmd->argv_types--; - - out: - silc_client_command_free(cmd); -} - -SILC_CLIENT_CMD_FUNC(whowas) -{ -} - -/* Command IDENTIFY. This command is used to query information about - specific user, especially ID's. */ - -SILC_CLIENT_CMD_FUNC(identify) -{ - SilcClientCommandContext cmd = (SilcClientCommandContext)context; - SilcBuffer buffer; - - if (cmd->argc < 2 || cmd->argc > 3) { - silc_say(cmd->client, "Usage: /IDENTIFY [@] []"); - goto out; - } - - if (!cmd->client->current_win->sock) { - silc_say(cmd->client, - "You are not connected to a server, use /SERVER to connect"); - goto out; - } - - buffer = silc_command_encode_payload(SILC_COMMAND_IDENTIFY, - cmd->argc - 1, ++cmd->argv, - ++cmd->argv_lens, ++cmd->argv_types); - silc_client_packet_send(cmd->client, cmd->client->current_win->sock, - SILC_PACKET_COMMAND, NULL, 0, NULL, NULL, - buffer->data, buffer->len, TRUE); - silc_buffer_free(buffer); - cmd->argv--; - cmd->argv_lens--; - cmd->argv_types--; - - out: - silc_client_command_free(cmd); -} - -/* Command NICK. Shows current nickname/sets new nickname on current - window. */ - -SILC_CLIENT_CMD_FUNC(nick) -{ - SilcClientCommandContext cmd = (SilcClientCommandContext)context; - SilcClientWindow win = NULL; - SilcBuffer buffer; - - if (!cmd->sock) { - silc_say(cmd->client, - "You are not connected to a server, use /SERVER to connect"); - goto out; - } - - /* Show current nickname */ - if (cmd->argc < 2) { - if (cmd->sock) { - silc_say(cmd->client, "Your nickname is %s on server %s", - win->nickname, win->remote_host); - } else { - silc_say(cmd->client, "Your nickname is %s", win->nickname); - } - goto out; - } - - win = (SilcClientWindow)cmd->sock->user_data; - - /* Set new nickname */ - buffer = silc_command_encode_payload(SILC_COMMAND_NICK, - cmd->argc - 1, ++cmd->argv, - ++cmd->argv_lens, ++cmd->argv_types); - silc_client_packet_send(cmd->client, cmd->sock, - SILC_PACKET_COMMAND, NULL, 0, NULL, NULL, - buffer->data, buffer->len, TRUE); - silc_buffer_free(buffer); - cmd->argv--; - cmd->argv_lens--; - cmd->argv_types--; - if (win->nickname) - silc_free(win->nickname); - win->nickname = strdup(cmd->argv[1]); - - out: - silc_client_command_free(cmd); -} - -/* Command SERVER. Connects to remote SILC server. This is local command. */ - -SILC_CLIENT_CMD_FUNC(server) -{ - SilcClientCommandContext cmd = (SilcClientCommandContext)context; - int len, port; - char *hostname; - - if (cmd->argc < 2) { - /* Show current servers */ - if (!cmd->client->current_win->sock) { - silc_say(cmd->client, "You are not connected to any server"); - silc_say(cmd->client, "Usage: /SERVER [[:]]"); - goto out; - } - - goto out; - } - - /* See if port is included and then extract it */ - if (strchr(cmd->argv[1], ':')) { - len = strcspn(cmd->argv[1], ":"); - hostname = silc_calloc(len + 1, sizeof(char)); - memcpy(hostname, cmd->argv[1], len); - port = atoi(cmd->argv[1] + 1 + len); - } else { - hostname = cmd->argv[1]; - /* XXX */ - port = 334; - } - - /* Connect asynchronously to not to block user interface */ - silc_client_connect_to_server(cmd->client, port, hostname); - - out: - silc_client_command_free(cmd); -} - -SILC_CLIENT_CMD_FUNC(list) -{ -} - -SILC_CLIENT_CMD_FUNC(topic) -{ -} - -SILC_CLIENT_CMD_FUNC(invite) -{ -} - -/* Command QUIT. Closes connection with current server. */ - -SILC_CLIENT_CMD_FUNC(quit) -{ - SilcClientCommandContext cmd = (SilcClientCommandContext)context; - SilcBuffer buffer; - - if (!cmd->client->current_win->sock) { - silc_say(cmd->client, - "You are not connected to a server, use /SERVER to connect"); - goto out; - } - - buffer = silc_command_encode_payload(SILC_COMMAND_QUIT, cmd->argc - 1, - ++cmd->argv, ++cmd->argv_lens, - ++cmd->argv_types); - silc_client_packet_send(cmd->client, cmd->client->current_win->sock, - SILC_PACKET_COMMAND, NULL, 0, NULL, NULL, - buffer->data, buffer->len, TRUE); - silc_buffer_free(buffer); - cmd->argv--; - cmd->argv_lens--; - cmd->argv_types--; - - /* Close connection */ - silc_client_close_connection(cmd->client, cmd->sock); - cmd->client->screen->bottom_line->connection = NULL; - silc_screen_print_bottom_line(cmd->client->screen, 0); - - silc_client_command_free(cmd); -} - -SILC_CLIENT_CMD_FUNC(kill) -{ -} - -SILC_CLIENT_CMD_FUNC(info) -{ -} - -SILC_CLIENT_CMD_FUNC(connect) -{ -} - -SILC_CLIENT_CMD_FUNC(ping) -{ -} - -SILC_CLIENT_CMD_FUNC(oper) -{ -} - -SILC_CLIENT_CMD_FUNC(trace) -{ -} - -SILC_CLIENT_CMD_FUNC(notice) -{ -} - -/* Command JOIN. Joins to a channel. */ - -SILC_CLIENT_CMD_FUNC(join) -{ - SilcClientCommandContext cmd = (SilcClientCommandContext)context; - SilcClientWindow win = NULL; - SilcIDCache *id_cache = NULL; - SilcBuffer buffer; - -#define CIDC(x) win->channel_id_cache[(x) - 32] -#define CIDCC(x) win->channel_id_cache_count[(x) - 32] - - if (cmd->argc < 2) { - /* Show channels currently joined to */ - if (!cmd->client->current_win->sock) { - silc_say(cmd->client, "No current channel for this window"); - silc_say(cmd->client, - "You are not connected to a server, use /SERVER to connect"); - goto out; - - } - - goto out; - } - - if (!cmd->client->current_win->sock) { - silc_say(cmd->client, - "You are not connected to a server, use /SERVER to connect"); - goto out; - } - - win = (SilcClientWindow)cmd->sock->user_data; - - /* See if we have joined to the requested channel already */ - silc_idcache_find_by_data(CIDC(cmd->argv[1][0]), CIDCC(cmd->argv[1][0]), - cmd->argv[1], &id_cache); - - if (id_cache) { - silc_say(cmd->client, "You are talking to channel %s", cmd->argv[1]); - win->current_channel = (SilcChannelEntry)id_cache->context; - cmd->client->screen->bottom_line->channel = cmd->argv[1]; - silc_screen_print_bottom_line(cmd->client->screen, 0); - goto out; - } - - /* Send JOIN command to the server */ - buffer = silc_command_encode_payload(SILC_COMMAND_JOIN, - cmd->argc - 1, ++cmd->argv, - ++cmd->argv_lens, ++cmd->argv_types); - silc_client_packet_send(cmd->client, cmd->client->current_win->sock, - SILC_PACKET_COMMAND, NULL, 0, NULL, NULL, - buffer->data, buffer->len, TRUE); - silc_buffer_free(buffer); - cmd->argv--; - cmd->argv_lens--; - cmd->argv_types--; - - out: - silc_client_command_free(cmd); -#undef CIDC -#undef CIDCC -} - -SILC_CLIENT_CMD_FUNC(motd) -{ -} - -SILC_CLIENT_CMD_FUNC(umode) -{ -} - -SILC_CLIENT_CMD_FUNC(cmode) -{ -} - -SILC_CLIENT_CMD_FUNC(kick) -{ -} - -SILC_CLIENT_CMD_FUNC(restart) -{ -} - -SILC_CLIENT_CMD_FUNC(close) -{ -} - -SILC_CLIENT_CMD_FUNC(die) -{ -} - -SILC_CLIENT_CMD_FUNC(silcoper) -{ -} - -SILC_CLIENT_CMD_FUNC(leave) -{ -} - -SILC_CLIENT_CMD_FUNC(names) -{ -} - -/* - * Local commands - */ - -/* HELP command. This is local command and shows help on SILC */ - -SILC_CLIENT_CMD_FUNC(help) -{ - -} - -/* CLEAR command. This is local command and clears current output window */ - -SILC_CLIENT_CMD_FUNC(clear) -{ - SilcClient client = (SilcClient)context; - - assert(client->current_win != NULL); - wclear((WINDOW *)client->current_win->screen); - wrefresh((WINDOW *)client->current_win->screen); -} - -/* VERSION command. This is local command and shows version of the client */ - -SILC_CLIENT_CMD_FUNC(version) -{ - -} - -/* Command MSG. Sends private message to user or list of users. */ -/* XXX supports only one destination */ - -SILC_CLIENT_CMD_FUNC(msg) -{ - SilcClientCommandContext cmd = (SilcClientCommandContext)context; - SilcClientWindow win = NULL; - SilcClient client = cmd->client; - SilcBuffer buffer; - SilcIDCache *id_cache; - unsigned int nick_len; - - if (cmd->argc < 3) { - silc_say(cmd->client, "Usage: /MSG "); - goto out; - } - - if (!cmd->client->current_win->sock) { - silc_say(cmd->client, - "You are not connected to a server, use /SERVER to connect"); - goto out; - } - - win = (SilcClientWindow)cmd->sock->user_data; - -#define CIDC(x) win->client_id_cache[(x) - 32], \ - win->client_id_cache_count[(x) - 32] - - /* Find ID from cache */ - if (silc_idcache_find_by_data(CIDC(cmd->argv[1][0]), cmd->argv[1], - &id_cache) == FALSE) { - SilcClientCommandContext ctx; - char ident[512]; - - SILC_LOG_DEBUG(("Requesting Client ID from server")); - - /* No ID found. Do query from the server. The query is done by - sending simple IDENTIFY command to the server. */ - ctx = silc_calloc(1, sizeof(*ctx)); - ctx->client = client; - ctx->sock = cmd->sock; - memset(ident, 0, sizeof(ident)); - snprintf(ident, sizeof(ident), "/IDENTIFY %s", cmd->argv[1]); - silc_client_parse_command_line(ident, &ctx->argv, &ctx->argv_lens, - &ctx->argv_types, &ctx->argc, 2); - silc_client_command_identify(ctx); - - /* Mark this command to be pending command and to be executed after - we have received the IDENTIFY reply from server. */ - silc_client_command_pending(SILC_COMMAND_IDENTIFY, - silc_client_command_msg, context); - return; - } - - /* Display the message for our eyes. */ - silc_print(client, "-> *%s* %s", cmd->argv[1], cmd->argv[2]); - - /* Send the private message */ - silc_client_packet_send_private_message(client, cmd->sock, id_cache->context, - cmd->argv[2], cmd->argv_lens[2], - TRUE); - out: - silc_client_command_free(cmd); -#undef CIDC -} - -SILC_CLIENT_CMD_FUNC(away) -{ -} diff --git a/apps/silc/command_reply.c b/apps/silc/command_reply.c deleted file mode 100644 index 491808ec..00000000 --- a/apps/silc/command_reply.c +++ /dev/null @@ -1,551 +0,0 @@ -/* - - 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. - */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ - -#include "clientincludes.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(away, AWAY), - 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(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, LEAVE), - - { 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(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_ON_CHANNEL), "User already on 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 } -}; - -/* Process received command reply. */ - -void silc_client_command_reply_process(SilcClient client, - SilcSocketConnection sock, - SilcBuffer buffer) -{ - SilcClientCommandReplyContext ctx; - SilcCommandPayload payload; - - /* Get command reply payload from packet */ - payload = silc_command_parse_payload(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; - - /* 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); - } -} - -/* 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; - SilcClient client = cmd->client; - SilcCommandStatus status; - unsigned char *tmp; - - SILC_LOG_DEBUG(("Start")); - - tmp = silc_command_get_arg_type(cmd->payload, 1, NULL); - SILC_GET16_MSB(status, tmp); - if (status != SILC_STATUS_OK) { - if (status == SILC_STATUS_ERR_NO_SUCH_NICK) { - tmp += 2; - silc_say(cmd->client, "%s: %s", tmp, - silc_client_command_status_message(status)); - goto out; - } else { - silc_say(cmd->client, "%s", silc_client_command_status_message(status)); - goto out; - } - } - - /* Display one whois reply */ - if (status == SILC_STATUS_OK) { - char buf[256]; - int argc, len; - unsigned char *id_data; - char *nickname = NULL, *username = NULL; - char *realname = NULL; - void *id; - - memset(buf, 0, sizeof(buf)); - - argc = silc_command_get_arg_num(cmd->payload); - id_data = silc_command_get_arg_type(cmd->payload, 2, NULL); - - nickname = silc_command_get_arg_type(cmd->payload, 3, &len); - if (nickname) { - strncat(buf, nickname, len); - strncat(buf, " is ", 4); - } - - username = silc_command_get_arg_type(cmd->payload, 4, &len); - if (username) { - strncat(buf, username, len); - } - - realname = silc_command_get_arg_type(cmd->payload, 5, &len); - if (realname) { - strncat(buf, " (", 2); - strncat(buf, realname, len); - strncat(buf, ")", 1); - } - -#if 0 - /* Save received Client ID to ID cache */ - /* XXX Maybe should not be saved as /MSG will get confused */ - id = silc_id_str2id(id_data, SILC_ID_CLIENT); - client->current_win->client_id_cache_count[(int)nickname[0] - 32] = - silc_idcache_add(&client->current_win-> - client_id_cache[(int)nickname[0] - 32], - client->current_win-> - client_id_cache_count[(int)nickname[0] - 32], - strdup(nickname), SILC_ID_CLIENT, id, NULL); -#endif - - silc_say(cmd->client, "%s", buf); - } - - if (status == SILC_STATUS_LIST_START) { - - } - - if (status == SILC_STATUS_LIST_END) { - - } - - SILC_CLIENT_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_WHOIS); - - out: - silc_client_command_reply_free(cmd); -} - -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; - SilcClientWindow win = (SilcClientWindow)cmd->sock->user_data; - SilcClientEntry client_entry; - SilcCommandStatus status; - unsigned char *tmp; - - SILC_LOG_DEBUG(("Start")); - -#define CIDC(x) win->client_id_cache[(x) - 32] -#define CIDCC(x) win->client_id_cache_count[(x) - 32] - - tmp = silc_command_get_arg_type(cmd->payload, 1, NULL); - SILC_GET16_MSB(status, tmp); - if (status != SILC_STATUS_OK) { - if (status == SILC_STATUS_ERR_NO_SUCH_NICK) { - tmp += 2; - silc_say(cmd->client, "%s: %s", tmp, - silc_client_command_status_message(status)); - goto out; - } else { - silc_say(cmd->client, "%s", silc_client_command_status_message(status)); - goto out; - } - } - - /* Display one whois reply */ - if (status == SILC_STATUS_OK) { - unsigned char *id_data; - char *nickname; - - id_data = silc_command_get_arg_type(cmd->payload, 2, NULL); - nickname = silc_command_get_arg_type(cmd->payload, 3, NULL); - - /* Allocate client entry */ - client_entry = silc_calloc(1, sizeof(*client_entry)); - client_entry->id = silc_id_str2id(id_data, SILC_ID_CLIENT); - client_entry->nickname = strdup(nickname); - - /* Save received Client ID to ID cache */ - CIDCC(nickname[0]) = - silc_idcache_add(&CIDC(nickname[0]), CIDCC(nickname[0]), - client_entry->nickname, SILC_ID_CLIENT, - client_entry->id, client_entry); - } - - 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); -#undef CIDC -#undef CIDCC -} - -/* 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; - SilcClientWindow win = (SilcClientWindow)cmd->sock->user_data; - SilcCommandStatus status; - unsigned char *tmp, *id_string; - int argc; - - SILC_LOG_DEBUG(("Start")); - - tmp = silc_command_get_arg_type(cmd->payload, 1, NULL); - SILC_GET16_MSB(status, tmp); - if (status != SILC_STATUS_OK) { - silc_say(cmd->client, "Cannot set nickname: %s", - silc_client_command_status_message(status)); - goto out; - } - - argc = silc_command_get_arg_num(cmd->payload); - if (argc < 2 || argc > 2) { - silc_say(cmd->client, "Cannot set nickname: bad reply to command"); - goto out; - } - - /* Take received Client ID */ - id_string = silc_command_get_arg_type(cmd->payload, 2, NULL); - silc_client_receive_new_id(cmd->client, cmd->sock, id_string); - - /* Update nickname on screen */ - cmd->client->screen->bottom_line->nickname = win->nickname; - silc_screen_print_bottom_line(cmd->client->screen, 0); - - out: - silc_client_command_reply_free(cmd); -} - -SILC_CLIENT_CMD_REPLY_FUNC(list) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(topic) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(invite) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(quit) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(kill) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(info) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(away) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(connect) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(ping) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(oper) -{ -} - -/* Received reply for JOIN command. */ - -SILC_CLIENT_CMD_REPLY_FUNC(join) -{ - SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; - SilcClient client = cmd->client; - SilcCommandStatus status; - unsigned int argc; - unsigned char *id_string; - char *topic, *tmp, *channel_name; - - SILC_LOG_DEBUG(("Start")); - - tmp = silc_command_get_arg_type(cmd->payload, 1, NULL); - SILC_GET16_MSB(status, tmp); - if (status != SILC_STATUS_OK) { - silc_say(cmd->client, "%s", silc_client_command_status_message(status)); - goto out; - } - - argc = silc_command_get_arg_num(cmd->payload); - if (argc < 3 || argc > 4) { - silc_say(cmd->client, "Cannot join channel: Bad reply packet"); - goto out; - } - - /* Get channel name */ - tmp = silc_command_get_arg_type(cmd->payload, 2, NULL); - channel_name = strdup(tmp); - - /* Get channel ID */ - id_string = silc_command_get_arg_type(cmd->payload, 3, NULL); - - /* Get topic */ - topic = silc_command_get_arg_type(cmd->payload, 4, NULL); - - /* Save received Channel ID */ - silc_client_new_channel_id(cmd->client, cmd->sock, channel_name, id_string); - - /* Print channel name on screen */ - client->screen->bottom_line->channel = channel_name; - silc_screen_print_bottom_line(client->screen, 0); - - if (topic) - silc_say(client, "Topic for %s: %s", channel_name, topic); - - out: - silc_client_command_reply_free(cmd); -} - -SILC_CLIENT_CMD_REPLY_FUNC(motd) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(umode) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(cmode) -{ -} - -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) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(leave) -{ -} - -SILC_CLIENT_CMD_REPLY_FUNC(names) -{ -} - -/* Private message received. This processes the private message and - finally displays it on the screen. */ - -SILC_CLIENT_CMD_REPLY_FUNC(msg) -{ - SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context; - SilcClient client = cmd->client; - SilcBuffer buffer = (SilcBuffer)cmd->context; - unsigned short nick_len; - unsigned char *nickname, *message; - SilcIDCache *id_cache; - unsigned char *id_string; - void *id; - - /* Get nickname */ - silc_buffer_unformat(buffer, - SILC_STR_UI16_NSTRING_ALLOC(&nickname, &nick_len), - SILC_STR_END); - silc_buffer_pull(buffer, 2 + nick_len); - -#if 0 - /* Get ID of the sender */ - id_string = silc_calloc(SILC_ID_CLIENT_LEN, sizeof(unsigned char *)); - silc_buffer_push(buffer, SILC_ID_CLIENT_LEN + SILC_ID_CLIENT_LEN); - memcpy(id_string, buffer->data, SILC_ID_CLIENT_LEN); - silc_buffer_pull(buffer, SILC_ID_CLIENT_LEN + SILC_ID_CLIENT_LEN); - id = silc_id_str2id(id_string, SILC_ID_CLIENT); - silc_free(id_string); - - /* Nickname should be verified if we don't have it in the cache */ - if (silc_idcache_find_by_data(client->current_win-> - client_id_cache[nickname[0] - 32], - client->current_win-> - client_id_cache_count[nickname[0] - 32], - nickname, &id_cache) == FALSE) { - - SilcClientCommandContext ctx; - char whois[255]; - - /* Private message from unknown source, try to resolve it. */ - - - return; - } -#endif - - message = silc_calloc(buffer->len + 1, sizeof(char)); - memcpy(message, buffer->data, buffer->len); - silc_print(client, "*%s* %s", nickname, message); - memset(message, 0, buffer->len); - silc_free(message); -} diff --git a/apps/silc/idlist.h b/apps/silc/idlist.h deleted file mode 100644 index 9c769e98..00000000 --- a/apps/silc/idlist.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - - idlist.h - - 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. - -*/ - -#ifndef IDLIST_H -#define IDLIST_H - -/* Client entry context. When client receives information about new client - (it receives its ID, for example, by IDENTIFY request) we create new - client entry. This entry also includes the private message keys if - they are used. */ -typedef struct SilcClientEntryStruct { - char *nickname; - SilcClientID *id; - - /* Keys, these are defined if private message key has been defined - with the remote client. */ - SilcCipher send_key; - SilcCipher receive_key; -} SilcClientEntryObject; - -typedef SilcClientEntryObject *SilcClientEntry; - -/* Channel entry context. This is allocate for every channel client has - joined to. This includes for example the channel specific keys */ -/* XXX channel_key is the server generated key. Later this context must - include the channel private key. */ -typedef struct SilcChannelEntryStruct { - char *channel_name; - SilcChannelID *id; - int on_channel; - - /* Channel keys */ - SilcCipher channel_key; - unsigned char *key; - unsigned int key_len; - unsigned char iv[SILC_CIPHER_MAX_IV_SIZE]; -} SilcChannelEntryObject; - -typedef SilcChannelEntryObject *SilcChannelEntry; - -#endif diff --git a/apps/silc/local_command.c b/apps/silc/local_command.c new file mode 100644 index 00000000..116f12fa --- /dev/null +++ b/apps/silc/local_command.c @@ -0,0 +1,802 @@ +/* + + local_command.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 "clientincludes.h" +#include "client_internal.h" + +/* Local commands. */ +SilcClientCommand silc_local_command_list[] = +{ + SILC_CLIENT_LCMD(help, HELP, "HELP", 0, 2), + SILC_CLIENT_LCMD(clear, CLEAR, "CLEAR", 0, 1), + SILC_CLIENT_LCMD(version, VERSION, "VERSION", 0, 1), + SILC_CLIENT_LCMD(server, SERVER, "SERVER", 0, 2), + SILC_CLIENT_LCMD(msg, MSG, "MSG", 0, 3), + SILC_CLIENT_LCMD(away, AWAY, "AWAY", 0, 2), + SILC_CLIENT_LCMD(key, KEY, "KEY", 0, 7), + SILC_CLIENT_LCMD(me, ME, "ME", 0, 3), + SILC_CLIENT_LCMD(notice, NOTICE, "NOTICE", 0, 3), + + { NULL, 0, NULL, 0, 0 }, +}; + +/* Finds and returns a pointer to the command list. Return NULL if the + command is not found. */ + +SilcClientCommand *silc_client_local_command_find(const char *name) +{ + SilcClientCommand *cmd; + + for (cmd = silc_local_command_list; cmd->name; cmd++) { + if (!strcmp(cmd->name, name)) + return cmd; + } + + return NULL; +} + +/* HELP command. This is local command and shows help on SILC */ + +SILC_CLIENT_LCMD_FUNC(help) +{ + +} + +/* CLEAR command. This is local command and clears current output window */ + +SILC_CLIENT_LCMD_FUNC(clear) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + + silc_client_command_free(cmd); +} + +/* VERSION command. This is local command and shows version of the client */ + +SILC_CLIENT_LCMD_FUNC(version) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClient client = cmd->client; + extern char *silc_version; + extern char *silc_name; + extern char *silc_fullname; + + silc_say(client, cmd->conn, + "%s (%s) version %s", silc_name, silc_fullname, + silc_version); + + silc_client_command_free(cmd); +} + +/* Command MSG. Sends private message to user or list of users. Note that + private messages are not really commands, they are message packets, + however, on user interface it is convenient to show them as commands + as that is the common way of sending private messages (like in IRC). */ + +SILC_CLIENT_LCMD_FUNC(msg) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClientConnection conn = cmd->conn; + SilcClient client = cmd->client; + SilcClientEntry client_entry = NULL; + uint32 num = 0; + char *nickname = NULL, *server = NULL; + + if (!cmd->conn) { + silc_say(client, conn, + "You are not connected to a server, use /SERVER to connect"); + goto out; + } + + if (cmd->argc < 3) { + silc_say(client, conn, "Usage: /MSG "); + goto out; + } + + /* Parse the typed nickname. */ + if (!silc_parse_nickname(cmd->argv[1], &nickname, &server, &num)) { + silc_say(client, conn, "Bad nickname"); + goto out; + } + + /* Find client entry */ + client_entry = silc_idlist_get_client(client, conn, nickname, server, num, + TRUE); + if (!client_entry) { + /* Client entry not found, it was requested thus mark this to be + pending command. */ + silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, conn->cmd_ident, + NULL, silc_client_local_command_msg, context); + return; + } + + /* Display the message for our eyes. */ + silc_print(client, "-> *%s* %s", cmd->argv[1], cmd->argv[2]); + + /* Send the private message */ + silc_client_send_private_message(client, conn, client_entry, 0, + cmd->argv[2], cmd->argv_lens[2], + TRUE); + + out: + silc_client_command_free(cmd); +} + + +/* Command SERVER. Connects to remote SILC server. This is local command. */ + +SILC_CLIENT_LCMD_FUNC(server) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClient client = cmd->client; + SilcClientConnection conn = cmd->conn; + int i = 0, len, port; + char *hostname; + + if (cmd->argc < 2) { + /* Show current servers */ + + if (!cmd->conn) { + silc_say(client, conn, "You are not connected to any server"); + silc_say(client, conn, "Usage: /SERVER [[:]]"); + goto out; + } + + silc_say(client, conn, "Current server: %s on %d %s", + conn->remote_host, conn->remote_port, + conn->remote_info ? conn->remote_info : ""); + + silc_say(client, conn, "Server list:"); + for (i = 0; i < client->conns_count; i++) { + silc_say(client, conn, " [%d] %s on %d %s", i + 1, + client->conns[i]->remote_host, + client->conns[i]->remote_port, + client->conns[i]->remote_info ? + client->conns[i]->remote_info : ""); + } + + goto out; + } + + /* See if port is included and then extract it */ + if (strchr(cmd->argv[1], ':')) { + len = strcspn(cmd->argv[1], ":"); + hostname = silc_calloc(len + 1, sizeof(char)); + memcpy(hostname, cmd->argv[1], len); + port = atoi(cmd->argv[1] + 1 + len); + } else { + hostname = cmd->argv[1]; + port = 706; + } + +#if 0 + if (conn && conn->remote_host) { + if (!strcmp(hostname, conn->remote_host) && port == conn->remote_port) { + silc_say(client, conn, "You are already connected to that server"); + goto out; + } + + /* Close connection */ + cmd->client->ops->disconnect(cmd->client, cmd->conn); + silc_client_close_connection(cmd->client, cmd->conn->sock); + } +#endif + + /* Connect asynchronously to not to block user interface */ + silc_client_connect_to_server(cmd->client, port, hostname, NULL); + + out: + silc_client_command_free(cmd); +} + +/* Local command AWAY. Client replies with away message to whomever sends + private message to the client if the away message is set. If this is + given without arguments the away message is removed. */ + +SILC_CLIENT_LCMD_FUNC(away) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClientConnection conn = cmd->conn; + SilcClient client = cmd->client; + SilcClientInternal app = (SilcClientInternal)client->application; + unsigned char modebuf[4]; + SilcBuffer idp, buffer; + + if (!cmd->conn) { + silc_say(client, conn, + "You are not connected to a server, use /SERVER to connect"); + goto out; + } + + if (cmd->argc == 1) { + conn->local_entry->mode &= ~SILC_UMODE_GONE; + + if (conn->away) { + silc_free(conn->away->away); + silc_free(conn->away); + conn->away = NULL; + app->screen->bottom_line->away = FALSE; + + silc_say(client, conn, "Away message removed"); + silc_screen_print_bottom_line(app->screen, 0); + } + } else { + conn->local_entry->mode |= SILC_UMODE_GONE; + + if (conn->away) + silc_free(conn->away->away); + else + conn->away = silc_calloc(1, sizeof(*conn->away)); + + app->screen->bottom_line->away = TRUE; + conn->away->away = strdup(cmd->argv[1]); + + silc_say(client, conn, "Away message set: %s", conn->away->away); + silc_screen_print_bottom_line(app->screen, 0); + } + + /* Send the UMODE command to se myself as gone */ + idp = silc_id_payload_encode(conn->local_id, SILC_ID_CLIENT); + SILC_PUT32_MSB(conn->local_entry->mode, modebuf); + buffer = silc_command_payload_encode_va(SILC_COMMAND_UMODE, + ++conn->cmd_ident, 2, + 1, idp->data, idp->len, + 2, modebuf, sizeof(modebuf)); + silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, + NULL, 0, NULL, NULL, buffer->data, + buffer->len, TRUE); + silc_buffer_free(buffer); + silc_buffer_free(idp); + + out: + silc_client_command_free(cmd); +} + +typedef struct { + int type; /* 1 = msg, 2 = channel */ +} *KeyInternal; + +static SilcSKEKeyMaterial *curr_key = NULL; + +/* Key agreement callback that is called after the key agreement protocol + has been performed. This is called also if error occured during the + key agreement protocol. The `key' is the allocated key material and + the caller is responsible of freeing it. The `key' is NULL if error + has occured. The application can freely use the `key' to whatever + purpose it needs. See lib/silcske/silcske.h for the definition of + the SilcSKEKeyMaterial structure. */ + +static void keyagr_completion(SilcClient client, + SilcClientConnection conn, + SilcClientEntry client_entry, + SilcKeyAgreementStatus status, + SilcSKEKeyMaterial *key, + void *context) +{ + KeyInternal i = (KeyInternal)context; + + curr_key = NULL; + + switch(status) { + case SILC_KEY_AGREEMENT_OK: + silc_say(client, conn, "Key agreement compeleted successfully with %s", + client_entry->nickname);; + + if (i->type == 1) { + /* Set the private key for this client */ + silc_client_del_private_message_key(client, conn, client_entry); + silc_client_add_private_message_key_ske(client, conn, client_entry, + NULL, key, FALSE); + silc_say(client, conn, "The private messages with the %s are now protected with the private key", client_entry->nickname); + silc_ske_free_key_material(key); + } + + break; + + case SILC_KEY_AGREEMENT_ERROR: + silc_say(client, conn, "Error occured during key agreement with %s", + client_entry->nickname); + break; + + case SILC_KEY_AGREEMENT_FAILURE: + silc_say(client, conn, "The key agreement failed with %s", + client_entry->nickname); + break; + + case SILC_KEY_AGREEMENT_TIMEOUT: + silc_say(client, conn, "Timeout during key agreement. The key agreement was not performed with %s", + client_entry->nickname); + break; + + default: + break; + } + + if (i) + silc_free(i); +} + +/* Local command KEY. This command is used to set and unset private + keys for channels, set and unset private keys for private messages + with remote clients and to send key agreement requests and + negotiate the key agreement protocol with remote client. The + key agreement is supported only to negotiate private message keys, + it currently cannot be used to negotiate private keys for channels, + as it is not convenient for that purpose. */ + +SILC_CLIENT_LCMD_FUNC(key) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClientConnection conn = cmd->conn; + SilcClient client = cmd->client; + SilcClientEntry client_entry = NULL; + SilcChannelEntry channel_entry = NULL; + uint32 num = 0; + char *nickname = NULL, *server = NULL; + int command = 0, port = 0, type = 0; + char *hostname = NULL; + KeyInternal internal = NULL; + + if (!cmd->conn) { + silc_say(client, conn, + "You are not connected to a server, use /SERVER to connect"); + goto out; + } + + if (cmd->argc < 4) { + silc_say(client, conn, "Usage: /KEY msg|channel " + "set|unset|agreement|negotiate []"); + goto out; + } + + /* Get type */ + if (!strcasecmp(cmd->argv[1], "msg")) + type = 1; + if (!strcasecmp(cmd->argv[1], "channel")) + type = 2; + + if (type == 0) { + silc_say(client, conn, "Usage: /KEY msg|channel " + "set|unset|agreement|negotiate []"); + goto out; + } + + if (type == 1) { + if (cmd->argv[2][0] == '*') { + nickname = "*"; + } else { + /* Parse the typed nickname. */ + if (!silc_parse_nickname(cmd->argv[2], &nickname, &server, &num)) { + silc_say(client, conn, "Bad nickname"); + goto out; + } + + /* Find client entry */ + client_entry = silc_idlist_get_client(client, conn, nickname, + server, num, TRUE); + if (!client_entry) { + /* Client entry not found, it was requested thus mark this to be + pending command. */ + silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, + conn->cmd_ident, + NULL, silc_client_local_command_key, + context); + return; + } + } + } + + if (type == 2) { + /* Get channel entry */ + char *name; + + if (cmd->argv[2][0] == '*') { + if (!conn->current_channel) { + silc_say(cmd->client, conn, "You are not on any channel"); + goto out; + } + name = conn->current_channel->channel_name; + } else { + name = cmd->argv[2]; + } + + channel_entry = silc_client_get_channel(client, conn, name); + if (!channel_entry) { + silc_say(client, conn, "You are not on that channel"); + goto out; + } + } + + /* Set command */ + if (!strcasecmp(cmd->argv[3], "set")) { + command = 1; + + if (cmd->argc == 4) { + if (curr_key && type == 1 && client_entry) { + silc_client_del_private_message_key(client, conn, client_entry); + silc_client_add_private_message_key_ske(client, conn, client_entry, + NULL, curr_key, FALSE); + goto out; + } + } + + if (cmd->argc >= 5) { + if (type == 1 && client_entry) { + /* Set private message key */ + + silc_client_del_private_message_key(client, conn, client_entry); + + if (cmd->argc >= 6) + silc_client_add_private_message_key(client, conn, client_entry, + cmd->argv[5], cmd->argv[4], + cmd->argv_lens[4], + (cmd->argv[4][0] == '*' ? + TRUE : FALSE), FALSE); + else + silc_client_add_private_message_key(client, conn, client_entry, + NULL, cmd->argv[4], + cmd->argv_lens[4], + (cmd->argv[4][0] == '*' ? + TRUE : FALSE), FALSE); + + /* Send the key to the remote client so that it starts using it + too. */ + silc_client_send_private_message_key(client, conn, client_entry, TRUE); + } else if (type == 2) { + /* Set private channel key */ + char *cipher = NULL, *hmac = NULL; + + if (!(channel_entry->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + silc_say(client, conn, + "Private key mode is not set on this channel"); + goto out; + } + + if (cmd->argc >= 6) + cipher = cmd->argv[5]; + if (cmd->argc >= 7) + hmac = cmd->argv[6]; + + if (!silc_client_add_channel_private_key(client, conn, channel_entry, + cipher, hmac, + cmd->argv[4], + cmd->argv_lens[4])) { + silc_say(client, conn, "Could not add channel private key"); + goto out; + } + } + } + + goto out; + } + + /* Unset command */ + if (!strcasecmp(cmd->argv[3], "unset")) { + command = 2; + + if (type == 1 && client_entry) { + /* Unset private message key */ + silc_client_del_private_message_key(client, conn, client_entry); + } else if (type == 2) { + /* Unset channel key(s) */ + SilcChannelPrivateKey *keys; + uint32 keys_count; + int number; + + if (cmd->argc == 4) + silc_client_del_channel_private_keys(client, conn, channel_entry); + + if (cmd->argc > 4) { + number = atoi(cmd->argv[4]); + keys = silc_client_list_channel_private_keys(client, conn, + channel_entry, + &keys_count); + if (!keys) + goto out; + + if (!number || number > keys_count) { + silc_client_free_channel_private_keys(keys, keys_count); + goto out; + } + + silc_client_del_channel_private_key(client, conn, channel_entry, + keys[number - 1]); + silc_client_free_channel_private_keys(keys, keys_count); + } + + goto out; + } + } + + /* List command */ + if (!strcasecmp(cmd->argv[3], "list")) { + command = 3; + + if (type == 1) { + SilcPrivateMessageKeys keys; + uint32 keys_count; + int k, i, len; + char buf[1024]; + + keys = silc_client_list_private_message_keys(client, conn, + &keys_count); + if (!keys) + goto out; + + /* list the private message key(s) */ + if (nickname[0] == '*') { + silc_say(client, conn, "Private message keys"); + silc_say(client, conn, + " Client Cipher Key"); + for (k = 0; k < keys_count; k++) { + memset(buf, 0, sizeof(buf)); + strncat(buf, " ", 2); + len = strlen(keys[k].client_entry->nickname); + strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len); + if (len < 30) + for (i = 0; i < 30 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + len = strlen(keys[k].cipher); + strncat(buf, keys[k].cipher, len > 14 ? 14 : len); + if (len < 14) + for (i = 0; i < 14 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + if (keys[k].key) + strcat(buf, ""); + else + strcat(buf, "*generated*"); + + silc_say(client, conn, "%s", buf); + } + } else { + silc_say(client, conn, "Private message key", + client_entry->nickname); + silc_say(client, conn, + " Client Cipher Key"); + for (k = 0; k < keys_count; k++) { + if (keys[k].client_entry != client_entry) + continue; + + memset(buf, 0, sizeof(buf)); + strncat(buf, " ", 2); + len = strlen(keys[k].client_entry->nickname); + strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len); + if (len < 30) + for (i = 0; i < 30 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + len = strlen(keys[k].cipher); + strncat(buf, keys[k].cipher, len > 14 ? 14 : len); + if (len < 14) + for (i = 0; i < 14 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + if (keys[k].key) + strcat(buf, ""); + else + strcat(buf, "*generated*"); + + silc_say(client, conn, "%s", buf); + } + } + + silc_client_free_private_message_keys(keys, keys_count); + } else if (type == 2) { + SilcChannelPrivateKey *keys; + uint32 keys_count; + int k, i, len; + char buf[1024]; + + keys = silc_client_list_channel_private_keys(client, conn, channel_entry, + &keys_count); + if (!keys) + goto out; + + silc_say(client, conn, "Channel %s private keys", + channel_entry->channel_name); + silc_say(client, conn, + " Cipher Hmac Key"); + for (k = 0; k < keys_count; k++) { + memset(buf, 0, sizeof(buf)); + strncat(buf, " ", 2); + + len = strlen(keys[k]->cipher->cipher->name); + strncat(buf, keys[k]->cipher->cipher->name, len > 16 ? 16 : len); + if (len < 16) + for (i = 0; i < 16 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + len = strlen(keys[k]->hmac->hmac->name); + strncat(buf, keys[k]->hmac->hmac->name, len > 16 ? 16 : len); + if (len < 16) + for (i = 0; i < 16 - len; i++) + strcat(buf, " "); + strcat(buf, " "); + + strcat(buf, ""); + + silc_say(client, conn, "%s", buf); + } + + silc_client_free_channel_private_keys(keys, keys_count); + } + + goto out; + } + + /* Send command is used to send key agreement */ + if (!strcasecmp(cmd->argv[3], "agreement")) { + command = 4; + + if (cmd->argc >= 5) + hostname = cmd->argv[4]; + if (cmd->argc >= 6) + port = atoi(cmd->argv[5]); + + internal = silc_calloc(1, sizeof(*internal)); + internal->type = type; + } + + /* Start command is used to start key agreement (after receiving the + key_agreement client operation). */ + if (!strcasecmp(cmd->argv[3], "negotiate")) { + command = 5; + + if (cmd->argc >= 5) + hostname = cmd->argv[4]; + if (cmd->argc >= 6) + port = atoi(cmd->argv[5]); + + internal = silc_calloc(1, sizeof(*internal)); + internal->type = type; + } + + if (command == 0) { + silc_say(client, conn, "Usage: /KEY msg|channel " + "set|unset|agreement|negotiate []"); + goto out; + } + + if (command == 4 && client_entry) { + silc_say(client, conn, "Sending key agreement to %s", cmd->argv[2]); + silc_client_send_key_agreement(client, conn, client_entry, hostname, + port, 120, keyagr_completion, internal); + goto out; + } + + if (command == 5 && client_entry) { + silc_say(client, conn, "Starting key agreement with %s", cmd->argv[2]); + silc_client_perform_key_agreement(client, conn, client_entry, hostname, + port, keyagr_completion, internal); + goto out; + } + + out: + if (nickname) + silc_free(nickname); + if (server) + silc_free(server); + silc_client_command_free(cmd); +} + +/* Sends an action to the channel. Equals CTCP's ACTION (IRC's /ME) + command. */ + +SILC_CLIENT_LCMD_FUNC(me) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClientConnection conn = cmd->conn; + SilcClient client = cmd->client; + SilcChannelEntry channel_entry; + char *name; + + if (!cmd->conn) { + silc_say(client, conn, + "You are not connected to a server, use /SERVER to connect"); + goto out; + } + + if (cmd->argc < 3) { + silc_say(client, conn, "Usage: /ME "); + goto out; + } + + if (cmd->argv[1][0] == '*') { + if (!conn->current_channel) { + silc_say(cmd->client, conn, "You are not on any channel"); + goto out; + } + name = conn->current_channel->channel_name; + } else { + name = cmd->argv[1]; + } + + channel_entry = silc_client_get_channel(client, conn, name); + if (!channel_entry) { + silc_say(client, conn, "You are not on that channel"); + goto out; + } + + /* Send the action message */ + silc_client_send_channel_message(client, conn, channel_entry, NULL, + SILC_MESSAGE_FLAG_ACTION, + cmd->argv[2], cmd->argv_lens[2], TRUE); + + silc_print(client, "* %s %s", conn->nickname, cmd->argv[2]); + + out: + silc_client_command_free(cmd); +} + +/* Sends an notice to the channel. */ + +SILC_CLIENT_LCMD_FUNC(notice) +{ + SilcClientCommandContext cmd = (SilcClientCommandContext)context; + SilcClientConnection conn = cmd->conn; + SilcClient client = cmd->client; + SilcChannelEntry channel_entry; + char *name; + + if (!cmd->conn) { + silc_say(client, conn, + "You are not connected to a server, use /SERVER to connect"); + goto out; + } + + if (cmd->argc < 3) { + silc_say(client, conn, "Usage: /NOTICE "); + goto out; + } + + if (cmd->argv[1][0] == '*') { + if (!conn->current_channel) { + silc_say(cmd->client, conn, "You are not on any channel"); + goto out; + } + name = conn->current_channel->channel_name; + } else { + name = cmd->argv[1]; + } + + channel_entry = silc_client_get_channel(client, conn, name); + if (!channel_entry) { + silc_say(client, conn, "You are not on that channel"); + goto out; + } + + /* Send the action message */ + silc_client_send_channel_message(client, conn, channel_entry, NULL, + SILC_MESSAGE_FLAG_NOTICE, + cmd->argv[2], cmd->argv_lens[2], TRUE); + + silc_print(client, "- %s %s", conn->nickname, cmd->argv[2]); + + out: + silc_client_command_free(cmd); +} diff --git a/apps/silc/local_command.h b/apps/silc/local_command.h new file mode 100644 index 00000000..134e0fdf --- /dev/null +++ b/apps/silc/local_command.h @@ -0,0 +1,61 @@ +/* + + local_command.h + + 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. + +*/ + +#ifndef LOCAL_COMMAND_H +#define LOCAL_COMMAND_H + +/* All local commands */ +extern SilcClientCommand silc_local_command_list[]; + +/* Local commands */ +#define SILC_LOCAL_COMMAND_HELP 1 +#define SILC_LOCAL_COMMAND_CLEAR 2 +#define SILC_LOCAL_COMMAND_VERSION 3 +#define SILC_LOCAL_COMMAND_SERVER 4 +#define SILC_LOCAL_COMMAND_MSG 5 +#define SILC_LOCAL_COMMAND_AWAY 6 +#define SILC_LOCAL_COMMAND_KEY 7 +#define SILC_LOCAL_COMMAND_ME 8 +#define SILC_LOCAL_COMMAND_NOTICE 9 + +/* Macros */ + +/* Macro used for command declaration in command list structure */ +#define SILC_CLIENT_LCMD(func, cmd, name, flags, args) \ +{ silc_client_local_command_##func, SILC_LOCAL_COMMAND_##cmd, \ + name, flags, args } + +/* Macro used to declare command functions */ +#define SILC_CLIENT_LCMD_FUNC(func) \ +void silc_client_local_command_##func(void *context) + +/* Prototypes */ +SilcClientCommand *silc_client_local_command_find(const char *name); +SILC_CLIENT_LCMD_FUNC(help); +SILC_CLIENT_LCMD_FUNC(clear); +SILC_CLIENT_LCMD_FUNC(version); +SILC_CLIENT_LCMD_FUNC(msg); +SILC_CLIENT_LCMD_FUNC(server); +SILC_CLIENT_LCMD_FUNC(away); +SILC_CLIENT_LCMD_FUNC(key); +SILC_CLIENT_LCMD_FUNC(me); +SILC_CLIENT_LCMD_FUNC(notice); + +#endif diff --git a/apps/silc/protocol.c b/apps/silc/protocol.c deleted file mode 100644 index ba89d9dc..00000000 --- a/apps/silc/protocol.c +++ /dev/null @@ -1,464 +0,0 @@ -/* - - protocol.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. - -*/ -/* - * Client side of the protocols. - */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ - -#include "clientincludes.h" - -SILC_TASK_CALLBACK(silc_client_protocol_connection_auth); -SILC_TASK_CALLBACK(silc_client_protocol_channel_auth); -SILC_TASK_CALLBACK(silc_client_protocol_key_exchange); - -/* SILC client protocol list */ -const SilcProtocolObject silc_protocol_list[] = -{ - { SILC_PROTOCOL_CLIENT_CONNECTION_AUTH, - silc_client_protocol_connection_auth }, - { SILC_PROTOCOL_CLIENT_CHANNEL_AUTH, - silc_client_protocol_channel_auth }, - { SILC_PROTOCOL_CLIENT_KEY_EXCHANGE, - silc_client_protocol_key_exchange }, - - { SILC_PROTOCOL_CLIENT_NONE, NULL }, -}; - -/* - * Key Exhange protocol functions - */ - -static void silc_client_protocol_ke_send_packet(SilcSKE ske, - SilcBuffer packet, - SilcPacketType type, - void *context) -{ - SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = - (SilcClientKEInternalContext *)protocol->context; - SilcClient client = (SilcClient)ctx->client; - - /* Send the packet immediately */ - silc_client_packet_send(client, ske->sock, type, NULL, 0, NULL, NULL, - packet->data, packet->len, TRUE); - -} - -static void silc_client_protocol_ke_phase1_cb(SilcSKE ske, - void *context) -{ - SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = - (SilcClientKEInternalContext *)protocol->context; - SilcClient client = (SilcClient)ctx->client; - - SILC_LOG_DEBUG(("Start")); - -} - -static void silc_client_protocol_ke_finish_cb(SilcSKE ske, - void *context) -{ - SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = - (SilcClientKEInternalContext *)protocol->context; - SilcClient client = (SilcClient)ctx->client; - - SILC_LOG_DEBUG(("Start")); - -} - -/* Sets the negotiated key material into use for particular connection. */ - -static void silc_client_protocol_ke_set_keys(SilcSKE ske, - SilcSocketConnection sock, - SilcSKEKeyMaterial *keymat, - SilcCipher cipher, - SilcPKCS pkcs, - SilcHash hash) -{ - SilcClientWindow win = (SilcClientWindow)sock->user_data; - SilcHash nhash; - - SILC_LOG_DEBUG(("Setting new keys into use")); - - /* Allocate cipher to be used in the communication */ - silc_cipher_alloc(cipher->cipher->name, &win->send_key); - silc_cipher_alloc(cipher->cipher->name, &win->receive_key); - - win->send_key->cipher->set_key(win->send_key->context, - keymat->send_enc_key, - keymat->enc_key_len); - win->send_key->set_iv(win->send_key, keymat->send_iv); - win->receive_key->cipher->set_key(win->receive_key->context, - keymat->receive_enc_key, - keymat->enc_key_len); - win->receive_key->set_iv(win->receive_key, keymat->receive_iv); - - /* Allocate PKCS to be used */ -#if 0 - /* XXX Do we ever need to allocate PKCS for the connection?? - If yes, we need to change KE protocol to get the initiators - public key. */ - silc_pkcs_alloc(pkcs->pkcs->name, &win->public_Key); - silc_pkcs_set_public_key(win->public_key, ske->ke2_payload->pk_data, - ske->ke2_payload->pk_len); -#endif - - /* Save HMAC key to be used in the communication. */ - silc_hash_alloc(hash->hash->name, &nhash); - silc_hmac_alloc(nhash, &win->hmac); - win->hmac_key_len = keymat->hmac_key_len; - win->hmac_key = silc_calloc(win->hmac_key_len, - sizeof(unsigned char)); - memcpy(win->hmac_key, keymat->hmac_key, keymat->hmac_key_len); - -} - -/* Performs key exchange protocol. This is used for both initiator - and responder key exchange. This may be called recursively. */ - -SILC_TASK_CALLBACK(silc_client_protocol_key_exchange) -{ - SilcProtocol protocol = (SilcProtocol)context; - SilcClientKEInternalContext *ctx = - (SilcClientKEInternalContext *)protocol->context; - SilcClient client = (SilcClient)ctx->client; - SilcSKEStatus status; - - SILC_LOG_DEBUG(("Start")); - - if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN) - protocol->state = SILC_PROTOCOL_STATE_START; - - switch(protocol->state) { - case SILC_PROTOCOL_STATE_START: - { - /* - * Start Protocol - */ - SilcSKE ske; - - /* Allocate Key Exchange object */ - ske = silc_ske_alloc(); - ctx->ske = ske; - - if (ctx->responder == TRUE) { -#if 0 - SilcBuffer start_payload; - - - /* Start the key exchange by processing the received security - properties packet from initiator. */ - status = silc_ske_responder_start(ske, ctx->rng, ctx->sock, - start_payload, - silc_client_protocol_ke_send_packet, - context); -#endif - } else { - SilcSKEStartPayload *start_payload; - - /* Assemble security properties. */ - silc_ske_assemble_security_properties(ske, &start_payload); - - /* Start the key exchange by sending our security properties - to the remote end. */ - status = silc_ske_initiator_start(ske, ctx->rng, ctx->sock, - start_payload, - silc_client_protocol_ke_send_packet, - context); - } - - if (status != SILC_SKE_STATUS_OK) { - switch(status) { - - default: - break; - } - } - - /* Advance the state of the protocol. */ - protocol->state++; - } - break; - case 2: - { - /* - * Phase 1 - */ - if (ctx->responder == TRUE) { -#if 0 - status = - silc_ske_responder_phase_1(ctx->ske, - ctx->ske->start_payload, - silc_server_protocol_ke_send_packet, - context); -#endif - } else { - /* Call Phase-1 function. This processes the Key Exchange Start - paylaod reply we just got from the responder. The callback - function will receive the processed payload where we will - save it. */ - status = - silc_ske_initiator_phase_1(ctx->ske, - ctx->packet, - silc_client_protocol_ke_phase1_cb, - context); - } - - switch(status) { - default: - break; - } - - /* Advance the state of the protocol and call the next state. */ - protocol->state++; - protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0); - } - break; - case 3: - { - /* - * Phase 2 - */ - if (ctx->responder == TRUE) { -#if 0 - status = - silc_ske_responder_phase_2(ctx->ske, - ctx->ske->start_payload, - silc_server_protocol_ke_send_packet, - context); -#endif - } else { - /* Call the Phase-2 function. This creates Diffie Hellman - key exchange parameters and sends our public part inside - Key Exhange 1 Payload to the responder. */ - status = - silc_ske_initiator_phase_2(ctx->ske, - silc_client_protocol_ke_send_packet, - context); - } - - switch(status) { - default: - break; - } - - /* Advance the state of the protocol. */ - protocol->state++; - } - break; - case 4: - { - /* - * Finish protocol - */ - if (ctx->responder == TRUE) { -#if 0 - status = - silc_ske_responder_phase_2(ctx->ske, - ctx->ske->start_payload, - silc_server_protocol_ke_send_packet, - context); -#endif - } else { - /* Finish the protocol. This verifies the Key Exchange 2 payload - sent by responder. */ - status = - silc_ske_initiator_finish(ctx->ske, - ctx->packet, - silc_client_protocol_ke_finish_cb, - context); - } - - switch(status) { - default: - break; - } - - /* Send Ok to the other end. We will end the protocol as server - sends Ok to us when we will take the new keys into use. */ - silc_ske_end(ctx->ske, silc_client_protocol_ke_send_packet, context); - - /* End the protocol on the next round */ - protocol->state = SILC_PROTOCOL_STATE_END; - } - break; - case SILC_PROTOCOL_STATE_END: - { - /* - * End protocol - */ - SilcSKEKeyMaterial *keymat; - - /* Process the key material */ - keymat = silc_calloc(1, sizeof(*keymat)); - silc_ske_process_key_material(ctx->ske, 16, (16 * 8), 16, keymat); - - /* Take the negotiated keys into use. */ - silc_client_protocol_ke_set_keys(ctx->ske, ctx->sock, keymat, - ctx->ske->prop->cipher, - ctx->ske->prop->pkcs, - ctx->ske->prop->hash); - - /* Protocol has ended, call the final callback */ - if (protocol->final_callback) - protocol->execute_final(client->timeout_queue, 0, protocol, fd); - else - silc_protocol_free(protocol); - } - break; - case SILC_PROTOCOL_STATE_ERROR: - - /* On error the final callback is always called. */ - /* protocol->final_callback(pptr, context);*/ - break; - case SILC_PROTOCOL_STATE_UNKNOWN: - break; - } -} - -/* - * Connection Authentication protocol functions - */ - -SILC_TASK_CALLBACK(silc_client_protocol_connection_auth) -{ - SilcProtocol protocol = (SilcProtocol)context; - SilcClientConnAuthInternalContext *ctx = - (SilcClientConnAuthInternalContext *)protocol->context; - SilcClient client = (SilcClient)ctx->client; - - SILC_LOG_DEBUG(("Start")); - - if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN) - protocol->state = SILC_PROTOCOL_STATE_START; - - switch(protocol->state) { - case SILC_PROTOCOL_STATE_START: - { - /* - * Start protocol. We send authentication data to the server - * to be authenticated. - */ - SilcBuffer packet; - int payload_len = 0; - unsigned char *auth_data = NULL; - unsigned int auth_data_len = 0; - - switch(ctx->auth_meth) { - case SILC_PROTOCOL_CONN_AUTH_NONE: - /* No authentication required */ - break; - - case SILC_PROTOCOL_CONN_AUTH_PASSWORD: - /* Password authentication */ - if (ctx->auth_data && ctx->auth_data_len) { - auth_data = ctx->auth_data; - auth_data_len = ctx->auth_data_len; - break; - } - - silc_say(client, "Password authentication required by server %s", - ctx->sock->hostname); - auth_data = silc_client_ask_passphrase(client); - auth_data_len = strlen(auth_data); - break; - - case SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY: -#if 0 - -#endif - break; - } - - payload_len = 4 + auth_data_len; - packet = silc_buffer_alloc(payload_len); - silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); - silc_buffer_format(packet, - SILC_STR_UI_SHORT(payload_len), - SILC_STR_UI_SHORT(SILC_SOCKET_TYPE_CLIENT), - SILC_STR_UI_XNSTRING(auth_data, auth_data_len), - SILC_STR_END); - - /* Send the packet to server */ - silc_client_packet_send(client, ctx->sock, - SILC_PACKET_CONNECTION_AUTH, - NULL, 0, NULL, NULL, - packet->data, packet->len, TRUE); - - if (auth_data) { - memset(auth_data, 0, auth_data_len); - silc_free(auth_data); - } - silc_buffer_free(packet); - - /* Next state is end of protocol */ - protocol->state = SILC_PROTOCOL_STATE_END; - } - break; - - case SILC_PROTOCOL_STATE_END: - { - /* - * End protocol. Nothing special to be done here. - */ - - /* Protocol has ended, call the final callback */ - if (protocol->final_callback) - protocol->execute_final(client->timeout_queue, 0, protocol, fd); - else - silc_protocol_free(protocol); - } - break; - - case SILC_PROTOCOL_STATE_ERROR: - { - /* - * Error - */ - - /* Error in protocol. Send FAILURE packet. Although I don't think - this could ever happen on client side. */ - silc_client_packet_send(client, ctx->sock, SILC_PACKET_FAILURE, - NULL, 0, NULL, NULL, NULL, 0, TRUE); - - /* On error the final callback is always called. */ - if (protocol->final_callback) - protocol->execute_final(client->timeout_queue, 0, protocol, fd); - else - silc_protocol_free(protocol); - } - break; - case SILC_PROTOCOL_STATE_UNKNOWN: - break; - } -} - -SILC_TASK_CALLBACK(silc_client_protocol_channel_auth) -{ -} diff --git a/apps/silc/protocol.h b/apps/silc/protocol.h deleted file mode 100644 index 1c40ef38..00000000 --- a/apps/silc/protocol.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - - protocol.h - - 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. - -*/ - -#ifndef PROTOCOL_H -#define PROTOCOL_H - -/* SILC client protocol types */ -#define SILC_PROTOCOL_CLIENT_NONE 0 -#define SILC_PROTOCOL_CLIENT_CONNECTION_AUTH 1 -#define SILC_PROTOCOL_CLIENT_CHANNEL_AUTH 2 -#define SILC_PROTOCOL_CLIENT_KEY_EXCHANGE 3 -/* #define SILC_PROTOCOL_CLIENT_MAX 255 */ - -/* Internal context for key exchange protocol */ -typedef struct { - void *client; - SilcSocketConnection sock; - SilcRng rng; - int responder; - SilcBuffer packet; - SilcSKE ske; -} SilcClientKEInternalContext; - -/* Internal context for connection authentication protocol */ -typedef struct { - void *client; - SilcSocketConnection sock; - - /* SKE object from Key Exchange protocol. */ - SilcSKE ske; - - /* Auth method that must be used. This is resolved before this - connection authentication protocol is started. */ - unsigned int auth_meth; - - /* Authentication data if we alreay know it. This is filled before - starting the protocol if we know the authentication data. Otherwise - these are and remain NULL. */ - unsigned char *auth_data; - unsigned int auth_data_len; - - SilcTask timeout_task; -} SilcClientConnAuthInternalContext; - -/* Prototypes */ - -#endif diff --git a/apps/silc/pubkey.pub b/apps/silc/pubkey.pub deleted file mode 100644 index 7dc0bfd0..00000000 Binary files a/apps/silc/pubkey.pub and /dev/null differ diff --git a/apps/silc/screen.c b/apps/silc/screen.c index f3f44567..8fb8a810 100644 --- a/apps/silc/screen.c +++ b/apps/silc/screen.c @@ -23,14 +23,7 @@ * old version of the SILC client dating back to 1997. */ /* XXX: Input line handling is really buggy! */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "clientincludes.h" @@ -38,16 +31,7 @@ SilcScreen silc_screen_init() { SilcScreen new; - new = silc_malloc(sizeof(*new)); - if (new == NULL) { - SILC_LOG_ERROR(("Could not create new screen object")); - return NULL; - } - - new->output_win_count = 0; - new->input_pos = 0; - new->cursor_pos = 0; - new->virtual_window = 0; + new = silc_calloc(1, sizeof(*new)); new->insert = TRUE; initscr(); @@ -68,7 +52,7 @@ WINDOW *silc_screen_create_output_window(SilcScreen screen) { assert(screen != NULL); - screen->output_win = silc_malloc(sizeof(*screen->output_win) * 1); + screen->output_win = silc_calloc(1, sizeof(*screen->output_win)); screen->output_win_count = 1; screen->output_win[0] = newwin(LINES - 3, COLS, 1, 0); scrollok(screen->output_win[0], TRUE); @@ -111,15 +95,20 @@ void silc_screen_create_input_window(SilcScreen screen) void silc_screen_init_upper_status_line(SilcScreen screen) { - int i; - int justify; - assert(screen != NULL); /* Create upper status line */ screen->upper_stat_line = newwin(0, COLS, 0, 0); scrollok(screen->upper_stat_line, FALSE); wattrset(screen->upper_stat_line, A_REVERSE); + + silc_screen_print_upper_stat_line(screen); +} + +void silc_screen_print_upper_stat_line(SilcScreen screen) +{ + int i; + int justify; /* Print empty line */ for (i = 0; i < COLS - 1; i++) @@ -130,13 +119,6 @@ void silc_screen_init_upper_status_line(SilcScreen screen) mvwprintw(screen->upper_stat_line, 0, 1, "%s %s", screen->u_stat_line.program_name, screen->u_stat_line.program_version); - /* - mvwprintw(screen->upper_stat_line, 0, justify, "[Your Connection: %s]", - stat.uconnect_status[stat.uconnect]); - mvwprintw(screen->upper_stat_line, 0, - (justify + justify + justify), "[SILC: %s]", - stat.silc_status[stat.silc]); - */ /* Prints clock on upper stat line */ silc_screen_print_clock(screen); @@ -225,6 +207,25 @@ void silc_screen_print_bottom_line(SilcScreen screen, int win_index) SILC_SCREEN_MAX_CHANNEL_LEN : len); } + if (line->channel_mode) { + len = strlen(line->channel_mode); + strncat(buf, " (+", 3); + strncat(buf, line->channel_mode, len > SILC_SCREEN_MAX_CHANNEL_LEN ? + SILC_SCREEN_MAX_CHANNEL_LEN : len); + strncat(buf, ")", 2); + } + + if (line->umode) { + len = strlen(line->umode); + strncat(buf, " [", 2); + strncat(buf, line->umode, len > SILC_SCREEN_MAX_UMODE_LEN ? + SILC_SCREEN_MAX_UMODE_LEN : len); + strncat(buf, "]", 2); + } + + if (line->away) + strncat(buf, " (away)", 8); + wattrset(screen->output_stat_line[win_index], A_REVERSE); for (i = 0; i < COLS - 10; i++) @@ -244,15 +245,20 @@ void silc_screen_refresh_all(SilcScreen screen) assert(screen != NULL); - redrawwin(screen->upper_stat_line); + wclear(screen->upper_stat_line); + silc_screen_print_upper_stat_line(screen); + + wclear(screen->output_stat_line[0]); + silc_screen_print_bottom_line(screen, 0); + silc_screen_print_coordinates(screen, 0); for (i = 0; i < screen->output_win_count; i++) { + wclear(screen->output_win[i]); wrefresh(screen->output_win[i]); - redrawwin(screen->output_win[i]); } + wclear(screen->input_win); wrefresh(screen->input_win); - redrawwin(screen->input_win); } /* Refreshes a window */ @@ -309,9 +315,9 @@ void silc_screen_input_backspace(SilcScreen screen) screen->virtual_window--; waddnstr(win, &buffer[screen->virtual_window * (COLS - 5)], COLS); - screen->input_pos = ((screen->virtual_window + 1) * (COLS - 5)) + 1; - screen->input_end = ((screen->virtual_window + 1) * (COLS - 5)) + 1; - screen->cursor_pos = (COLS - 5) + 1; + screen->input_pos = ((screen->virtual_window + 1) * (COLS - 5)); + screen->input_end = ((screen->virtual_window + 1) * (COLS - 5)); + screen->cursor_pos = (COLS - 5); wrefresh(win); } } @@ -413,8 +419,8 @@ void silc_screen_input_cursor_left(SilcScreen screen) screen->virtual_window--; waddnstr(win, &buffer[screen->virtual_window * (COLS - 5)], COLS); - screen->input_pos = ((screen->virtual_window + 1) * (COLS - 5)) + 1; - screen->cursor_pos = (COLS - 5) + 1; + screen->input_pos = ((screen->virtual_window + 1) * (COLS - 5)); + screen->cursor_pos = (COLS - 5); wrefresh(win); } } diff --git a/apps/silc/screen.h b/apps/silc/screen.h index 25d083b0..d98d1b6c 100644 --- a/apps/silc/screen.h +++ b/apps/silc/screen.h @@ -26,6 +26,9 @@ typedef struct { char *nickname; char *connection; char *channel; + char *channel_mode; + char *umode; + int away; } *SilcScreenBottomLine; typedef struct { @@ -35,14 +38,14 @@ typedef struct { /* Output windows */ WINDOW **output_win; WINDOW **output_stat_line; - unsigned int output_win_count; + uint32 output_win_count; /* Input window at the bottom of the screen */ WINDOW *input_win; unsigned char *input_buffer; - unsigned int input_pos; - unsigned int input_end; - unsigned int cursor_pos; + uint32 input_pos; + uint32 input_end; + uint32 cursor_pos; int virtual_window; /* Bottom line on screen */ @@ -53,8 +56,8 @@ typedef struct { /* XXX */ struct upper_status_line { - char *program_name; - char *program_version; + const char *program_name; + const char *program_version; } u_stat_line; } SilcScreenObject; @@ -75,6 +78,9 @@ typedef SilcScreenObject *SilcScreen; /* Maximum length of connection name that will be shown on the screen */ #define SILC_SCREEN_MAX_CONN_LEN 20 +/* Maximum length of user mode that will be shown on the screen */ +#define SILC_SCREEN_MAX_UMODE_LEN 20 + /* Macros */ /* Macro used to insert typed character into the buffer. The character @@ -103,6 +109,7 @@ WINDOW *silc_screen_create_output_window(SilcScreen screen); WINDOW *silc_screen_add_output_window(SilcScreen screen); void silc_screen_create_input_window(SilcScreen screen); void silc_screen_init_upper_status_line(SilcScreen screen); +void silc_screen_print_upper_stat_line(SilcScreen screen); void silc_screen_init_output_status_line(SilcScreen screen); void silc_screen_print_clock(SilcScreen screen); void silc_screen_print_coordinates(SilcScreen screen, int win_index); diff --git a/apps/silc/silc.c b/apps/silc/silc.c index e510ad2d..47f39dd9 100644 --- a/apps/silc/silc.c +++ b/apps/silc/silc.c @@ -17,18 +17,24 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "clientincludes.h" #include "version.h" +/* Static function prototypes */ +static int silc_client_bad_keys(unsigned char key); +static void silc_client_clear_input(SilcClientInternal app); +static void silc_client_process_message(SilcClientInternal app); +static char *silc_client_parse_command(unsigned char *buffer); + +void silc_client_create_main_window(SilcClientInternal app); + +/* Static task callback prototypes */ +SILC_TASK_CALLBACK(silc_client_update_clock); +SILC_TASK_CALLBACK(silc_client_run_commands); +SILC_TASK_CALLBACK(silc_client_process_key_press); + /* Long command line options */ static struct option long_opts[] = { @@ -42,6 +48,7 @@ static struct option long_opts[] = { "private-key", 1, NULL, 'k' }, { "config-file", 1, NULL, 'f' }, { "no-silcrc", 0, NULL, 'q' }, + { "debug", 0, NULL, 'd' }, { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'V' }, { "list-ciphers", 0, NULL, 1 }, @@ -52,6 +59,7 @@ static struct option long_opts[] = { "create-key-pair", 0, NULL, 'C' }, { "pkcs", 1, NULL, 10 }, { "bits", 1, NULL, 11 }, + { "show-key", 1, NULL, 'S' }, { NULL, 0, NULL, 0 } }; @@ -65,12 +73,17 @@ static char *opt_cipher = NULL; static char *opt_public_key = NULL; static char *opt_private_key = NULL; static char *opt_config_file = NULL; -static int opt_no_silcrc = FALSE; +static bool opt_no_silcrc = FALSE; -static int opt_create_keypair = FALSE; +static bool opt_create_keypair = FALSE; +static bool opt_show_key = FALSE; static char *opt_pkcs = NULL; +static char *opt_keyfile = NULL; static int opt_bits = 0; +/* SILC Client operations */ +extern SilcClientOperations ops; + /* Prints out the usage of silc client */ void usage() @@ -88,6 +101,7 @@ Usage: silc [options]\n\ -k, --private-key=FILE Private key used in SILC\n\ -f, --config-file=FILE Alternate configuration file\n\ -q, --no-silcrc Don't load ~/.silcrc on startup\n\ + -d, --debug Enable debugging\n\ -h, --help Display this help message\n\ -V, --version Display version\n\ --list-ciphers List supported ciphers\n\ @@ -98,6 +112,7 @@ Usage: silc [options]\n\ -C, --create-key-pair Create new public key pair\n\ --pkcs=PKCS Set the PKCS of the public key pair\n\ --bits=VALUE Set length of the public key pair\n\ + -S, --show-key=FILE Show the contents of the public key\n\ \n"); } @@ -106,13 +121,15 @@ int main(int argc, char **argv) int opt, option_index = 1; int ret; SilcClient silc = NULL; - SilcClientConfig config = NULL; - + SilcClientInternal app = NULL; + + silc_debug = FALSE; + if (argc > 1) { while ((opt = getopt_long(argc, argv, - "s:p:n:c:b:k:f:qhVC", + "s:p:n:c:b:k:f:qdhVCS:", long_opts, &option_index)) != EOF) { switch(opt) @@ -155,6 +172,9 @@ int main(int argc, char **argv) case 'q': opt_no_silcrc = TRUE; break; + case 'd': + silc_debug = TRUE; + break; case 'h': usage(); exit(0); @@ -194,6 +214,11 @@ SILC Secure Internet Live Conferencing, version %s\n", if (optarg) opt_bits = atoi(optarg); break; + case 'S': + opt_show_key = TRUE; + if (optarg) + opt_keyfile = strdup(optarg); + break; default: exit(0); @@ -214,36 +239,125 @@ SILC Secure Internet Live Conferencing, version %s\n", signal(SIGFPE, SIG_DFL); // signal(SIGINT, SIG_IGN); - /* Default configuration file */ - if (!opt_config_file) - opt_config_file = strdup(SILC_CLIENT_CONFIG_FILE); - - /* Read global configuration file. */ - config = silc_client_config_alloc(opt_config_file); - if (config == NULL) - goto fail; +#ifdef SOCKS + /* Init SOCKS */ + SOCKSinit(argv[0]); +#endif if (opt_create_keypair == TRUE) { /* Create new key pair and exit */ - silc_client_create_key_pair(opt_pkcs, opt_bits); + silc_cipher_register_default(); + silc_pkcs_register_default(); + silc_hash_register_default(); + silc_hmac_register_default(); + silc_client_create_key_pair(opt_pkcs, opt_bits, + NULL, NULL, NULL, NULL, NULL); + silc_free(opt_pkcs); exit(0); } - /* Read local configuration file */ + if (opt_show_key == TRUE) { + /* Dump the key */ + silc_cipher_register_default(); + silc_pkcs_register_default(); + silc_hash_register_default(); + silc_hmac_register_default(); + silc_client_show_key(opt_keyfile); + silc_free(opt_keyfile); + exit(0); + } + + /* Default configuration file */ + if (!opt_config_file) + opt_config_file = strdup(SILC_CLIENT_CONFIG_FILE); + /* Allocate internal application context */ + app = silc_calloc(1, sizeof(*app)); /* Allocate new client */ - ret = silc_client_alloc(&silc); - if (ret == FALSE) + app->client = silc = silc_client_alloc(&ops, app, silc_version_string); + if (!silc) goto fail; - /* Initialize the client */ - silc->config = config; + /* Read global configuration file. */ + app->config = silc_client_config_alloc(opt_config_file); + + /* XXX Read local configuration file */ + + /* Get user information */ + silc->username = silc_get_username(); + silc->hostname = silc_net_localhost(); + silc->realname = silc_get_real_name(); + + /* Register all configured ciphers, PKCS and hash functions. */ + if (app->config) { + app->config->client = (void *)app; + if (!silc_client_config_register_ciphers(app->config)) + silc_cipher_register_default(); + if (!silc_client_config_register_pkcs(app->config)) + silc_pkcs_register_default(); + if (!silc_client_config_register_hashfuncs(app->config)) + silc_hash_register_default(); + if (!silc_client_config_register_hmacs(app->config)) + silc_hmac_register_default(); + } else { + /* Register default ciphers, pkcs, hash funtions and hmacs. */ + silc_cipher_register_default(); + silc_pkcs_register_default(); + silc_hash_register_default(); + silc_hmac_register_default(); + } + + /* Check ~/.silc directory and public and private keys */ + if (silc_client_check_silc_dir() == FALSE) + goto fail; + + /* Load public and private key */ + if (silc_client_load_keys(silc) == FALSE) + goto fail; + + /* Initialize the client. This initializes the client library and + sets everything ready for silc_client_run. */ ret = silc_client_init(silc); if (ret == FALSE) goto fail; - /* Run the client */ + /* Register the main task that is used in client. This receives + the key pressings. */ + silc_task_register(silc->io_queue, fileno(stdin), + silc_client_process_key_press, + (void *)silc, 0, 0, + SILC_TASK_FD, + SILC_TASK_PRI_NORMAL); + + /* Register timeout task that updates clock every minute. */ + silc_task_register(silc->timeout_queue, 0, + silc_client_update_clock, + (void *)silc, + silc_client_time_til_next_min(), 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_LOW); + + if (app->config && app->config->commands) { + /* Run user configured commands with timeout */ + silc_task_register(silc->timeout_queue, 0, + silc_client_run_commands, + (void *)silc, 0, 1, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_LOW); + } + + /* Allocate the input buffer used to save typed characters */ + app->input_buffer = silc_buffer_alloc(SILC_SCREEN_INPUT_WIN_SIZE); + silc_buffer_pull_tail(app->input_buffer, + SILC_BUFFER_END(app->input_buffer)); + + /* Initialize the screen */ + silc_client_create_main_window(app); + silc_screen_print_coordinates(app->screen, 0); + + /* Run the client. When this returns the application will be + terminated. */ silc_client_run(silc); /* Stop the client. This probably has been done already but it @@ -254,9 +368,332 @@ SILC Secure Internet Live Conferencing, version %s\n", exit(0); fail: - if (config) - silc_client_config_free(config); + if (opt_config_file) + silc_free(opt_config_file); + if (app->config) + silc_client_config_free(app->config); if (silc) silc_client_free(silc); exit(1); } + +/* Creates the main window used in SILC client. This is called always + at the initialization of the client. If user wants to create more + than one windows a new windows are always created by calling + silc_client_add_window. */ + +void silc_client_create_main_window(SilcClientInternal app) +{ + void *screen; + + SILC_LOG_DEBUG(("Creating main window")); + + app->screen = silc_screen_init(); + app->screen->input_buffer = app->input_buffer->data; + app->screen->u_stat_line.program_name = silc_name; + app->screen->u_stat_line.program_version = silc_version; + + /* Create the actual screen */ + screen = (void *)silc_screen_create_output_window(app->screen); + silc_screen_create_input_window(app->screen); + silc_screen_init_upper_status_line(app->screen); + silc_screen_init_output_status_line(app->screen); + + app->screen->bottom_line->nickname = silc_get_username(); + silc_screen_print_bottom_line(app->screen, 0); +} + +/* The main task on SILC client. This processes the key pressings user + has made. */ + +SILC_TASK_CALLBACK(silc_client_process_key_press) +{ + SilcClient client = (SilcClient)context; + SilcClientInternal app = (SilcClientInternal)client->application; + int c; + + /* There is data pending in stdin, this gets it directly */ + c = wgetch(app->screen->input_win); + if (silc_client_bad_keys(c)) + return; + + SILC_LOG_DEBUG(("Pressed key: %d", c)); + + switch(c) { + /* + * Special character handling + */ + case KEY_UP: + case KEY_DOWN: + break; + case KEY_RIGHT: + /* Right arrow */ + SILC_LOG_DEBUG(("RIGHT")); + silc_screen_input_cursor_right(app->screen); + break; + case KEY_LEFT: + /* Left arrow */ + SILC_LOG_DEBUG(("LEFT")); + silc_screen_input_cursor_left(app->screen); + break; + case KEY_BACKSPACE: + case KEY_DC: + case '\177': + case '\b': + /* Backspace */ + silc_screen_input_backspace(app->screen); + break; + case '\011': + /* Tabulator */ + break; + case KEY_IC: + /* Insert switch. Turns on/off insert on input window */ + silc_screen_input_insert(app->screen); + break; + case CTRL('j'): + case '\r': + /* Enter, Return. User pressed enter we are ready to + process the message. */ + silc_client_process_message(app); + break; + case CTRL('l'): + /* Refresh screen, Ctrl^l */ + silc_screen_refresh_all(app->screen); + break; + case CTRL('a'): + case KEY_HOME: +#ifdef KEY_BEG + case KEY_BEG: +#endif + /* Beginning, Home */ + silc_screen_input_cursor_home(app->screen); + break; + case CTRL('e'): +#ifdef KEY_END + case KEY_END: +#endif + case KEY_LL: + /* End */ + silc_screen_input_cursor_end(app->screen); + break; + case CTRL('g'): + /* Bell, Ctrl^g */ + beep(); + break; + case KEY_DL: + case CTRL('u'): + /* Delete line */ + silc_client_clear_input(app); + break; + default: + /* + * Other characters + */ + if (c < 32) { + /* Control codes are printed as reversed */ + c = (c & 127) | 64; + wattron(app->screen->input_win, A_REVERSE); + silc_screen_input_print(app->screen, c); + wattroff(app->screen->input_win, A_REVERSE); + } else { + /* Normal character */ + silc_screen_input_print(app->screen, c); + } + } + + silc_screen_print_coordinates(app->screen, 0); + silc_screen_refresh_win(app->screen->input_win); +} + +static int silc_client_bad_keys(unsigned char key) +{ + /* these are explained in curses.h */ + switch(key) { + case KEY_SF: + case KEY_SR: + case KEY_NPAGE: + case KEY_PPAGE: + case KEY_PRINT: + case KEY_A1: + case KEY_A3: + case KEY_B2: + case KEY_C1: + case KEY_C3: +#ifdef KEY_UNDO + case KEY_UNDO: +#endif +#ifdef KEY_EXIT + case KEY_EXIT: +#endif + case '\v': /* VT */ + case '\E': /* we ignore ESC */ + return TRUE; + default: + return FALSE; + } +} + +/* Clears input buffer */ + +static void silc_client_clear_input(SilcClientInternal app) +{ + silc_buffer_clear(app->input_buffer); + silc_buffer_pull_tail(app->input_buffer, + SILC_BUFFER_END(app->input_buffer)); + silc_screen_input_reset(app->screen); +} + +/* Processes messages user has typed on the screen. This either sends + a packet out to network or if command were written executes it. */ + +static void silc_client_process_message(SilcClientInternal app) +{ + unsigned char *data; + uint32 len; + + SILC_LOG_DEBUG(("Start")); + + data = app->input_buffer->data; + len = strlen(data); + + if (data[0] == '/' && data[1] != ' ') { + /* Command */ + uint32 argc = 0; + unsigned char **argv, *tmpcmd; + uint32 *argv_lens, *argv_types; + SilcClientCommand *cmd; + SilcClientCommandContext ctx; + + /* Get the command */ + tmpcmd = silc_client_parse_command(data); + cmd = silc_client_local_command_find(tmpcmd); + if (!cmd && (cmd = silc_client_command_find(tmpcmd)) == NULL) { + silc_say(app->client, app->current_win, "Invalid command: %s", tmpcmd); + silc_free(tmpcmd); + goto out; + } + + /* Now parse all arguments */ + silc_parse_command_line(data + 1, &argv, &argv_lens, + &argv_types, &argc, cmd->max_args); + silc_free(tmpcmd); + + SILC_LOG_DEBUG(("Executing command: %s", cmd->name)); + + /* Allocate command context. This and its internals must be free'd + by the command routine receiving it. */ + ctx = silc_client_command_alloc(); + ctx->client = app->client; + ctx->conn = app->conn; + ctx->command = cmd; + ctx->argc = argc; + ctx->argv = argv; + ctx->argv_lens = argv_lens; + ctx->argv_types = argv_types; + + /* Execute command */ + (*cmd->cb)(ctx); + + } else { + /* Normal message to a channel */ + if (len && app->conn && app->conn->current_channel && + app->conn->current_channel->on_channel == TRUE) { + silc_print(app->client, "> %s", data); + silc_client_send_channel_message(app->client, + app->conn, + app->conn->current_channel, NULL, + 0, data, strlen(data), TRUE); + } + } + + out: + /* Clear the input buffer */ + silc_client_clear_input(app); +} + +/* Returns the command fetched from user typed command line */ + +static char *silc_client_parse_command(unsigned char *buffer) +{ + char *ret; + const char *cp = buffer; + int len; + + len = strcspn(cp, " "); + ret = silc_to_upper((char *)++cp); + ret[len - 1] = 0; + + return ret; +} + +/* Updates clock on the screen every minute. */ + +SILC_TASK_CALLBACK(silc_client_update_clock) +{ + SilcClient client = (SilcClient)context; + SilcClientInternal app = (SilcClientInternal)client->application; + + /* Update the clock on the screen */ + silc_screen_print_clock(app->screen); + + /* Re-register this same task */ + silc_task_register(qptr, 0, silc_client_update_clock, context, + silc_client_time_til_next_min(), 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_LOW); + + silc_screen_refresh_win(app->screen->input_win); +} + +/* Runs commands user configured in configuration file. This is + called when initializing client. */ + +SILC_TASK_CALLBACK(silc_client_run_commands) +{ + SilcClient client = (SilcClient)context; + SilcClientInternal app = (SilcClientInternal)client->application; + SilcClientConfigSectionCommand *cs; + + SILC_LOG_DEBUG(("Start")); + + cs = app->config->commands; + while(cs) { + uint32 argc = 0; + unsigned char **argv, *tmpcmd; + uint32 *argv_lens, *argv_types; + SilcClientCommand *cmd; + SilcClientCommandContext ctx; + + /* Get the command */ + tmpcmd = silc_client_parse_command(cs->command); + cmd = silc_client_local_command_find(tmpcmd); + if (!cmd && (cmd = silc_client_command_find(tmpcmd)) == NULL) { + silc_say(client, app->conn, "Invalid command: %s", tmpcmd); + silc_free(tmpcmd); + continue; + } + + /* Now parse all arguments */ + silc_parse_command_line(cs->command + 1, &argv, &argv_lens, + &argv_types, &argc, cmd->max_args); + silc_free(tmpcmd); + + SILC_LOG_DEBUG(("Executing command: %s", cmd->name)); + + /* Allocate command context. This and its internals must be free'd + by the command routine receiving it. */ + ctx = silc_client_command_alloc(); + ctx->client = client; + ctx->conn = app->conn; + ctx->command = cmd; + ctx->argc = argc; + ctx->argv = argv; + ctx->argv_lens = argv_lens; + ctx->argv_types = argv_types; + + /* Execute command */ + (*cmd->cb)(ctx); + + cs = cs->next; + } +} diff --git a/apps/silc/silc.h b/apps/silc/silc.h index baa83b34..a5d75d90 100644 --- a/apps/silc/silc.h +++ b/apps/silc/silc.h @@ -32,4 +32,48 @@ home directory. This may override global configuration settings. */ #define SILC_CLIENT_HOME_CONFIG_FILE ".silcrc" +/* Default public and private key file names */ +#define SILC_CLIENT_PUBLIC_KEY_NAME "public_key.pub" +#define SILC_CLIENT_PRIVATE_KEY_NAME "private_key.prv" + +/* Default key expiration time, one year. */ +#define SILC_CLIENT_KEY_EXPIRES 365 + +/* Default settings for creating key pair */ +#define SILC_CLIENT_DEF_PKCS "rsa" +#define SILC_CLIENT_DEF_PKCS_LEN 1024 + +/* XXX This is entirely temporary structure until UI is written again. */ +typedef struct { + /* Input buffer that holds the characters user types. This is + used only to store the typed chars for a while. */ + SilcBuffer input_buffer; + + /* The SILC client screen object */ + SilcScreen screen; + + /* Current physical window */ + void *current_win; + + SilcClientConnection conn; + + /* Configuration object */ + SilcClientConfig config; + +#ifdef SILC_SIM + /* SIM (SILC Module) table */ + SilcSimContext **sim; + uint32 sim_count; +#endif + + /* The allocated client */ + SilcClient client; +} *SilcClientInternal; + +/* Macros */ + +#ifndef CTRL +#define CTRL(x) ((x) & 0x1f) /* Ctrl+x */ +#endif + #endif diff --git a/apps/silc/testi.conf b/apps/silc/testi.conf deleted file mode 100644 index 72dbaed5..00000000 --- a/apps/silc/testi.conf +++ /dev/null @@ -1,21 +0,0 @@ -[cipher] -twofish:/home/priikone/silc/lib/silcsim/modules/twofish.sim.so:16:16 -rc6:/home/priikone/silc/lib/silcsim/modules/rc6.sim.so:16:16 -mars:/home/priikone/silc/lib/silcsim/modules/mars.sim.so:16:16 -none:/home/priikone/silc/lib/silcsim/modules/none.sim.so:0:0 - -[hash] -md5::64:16 -sha1::64:20 - -#[pkcs] -#rsa::1024 -#dss::1024 - -[connection] -#lassi.kuo.fi.ssh.com:passwd::1333 - -[commands] -#/server lassi.kuo.fi.ssh.com:1333 -#/server lassi:1334 -#/server leevi:1333 diff --git a/apps/silc/testi2.conf b/apps/silc/testi2.conf deleted file mode 100644 index e1fa600f..00000000 --- a/apps/silc/testi2.conf +++ /dev/null @@ -1,21 +0,0 @@ -[cipher] -twofish:/home/priikone/silc/lib/silcsim/modules/twofish.sim.so:16:16 -rc6:/home/priikone/silc/lib/silcsim/modules/rc6.sim.so:16:16 -mars:/home/priikone/silc/lib/silcsim/modules/mars.sim.so:16:16 -none:/home/priikone/silc/lib/silcsim/modules/none.sim.so:0:0 - -[hash] -md5::64:16 -sha1::64:20 - -#[pkcs] -#rsa::1024 -#dss::1024 - -[connection] -#lassi.kuo.fi.ssh.com:passwd::1333 - -[commands] -#/server lassi:1333 -/server lassi:1334 -#/server leevi:1333 diff --git a/apps/silcd/Makefile.am b/apps/silcd/Makefile.am index 385b8ccd..3322c946 100644 --- a/apps/silcd/Makefile.am +++ b/apps/silcd/Makefile.am @@ -18,25 +18,29 @@ AUTOMAKE_OPTIONS = 1.0 no-dependencies foreign -bin_PROGRAMS = silcd +sbin_PROGRAMS = silcd silcd_SOURCES = \ protocol.c \ route.c \ - server.c \ + packet_send.c \ + packet_receive.c \ idlist.c \ command.c \ command_reply.c \ + silcd.c \ + server.c \ + server_util.c \ + server_backup.c \ serverconfig.c \ serverid.c \ - silcd.c \ server_version.c -LDADD = -L. -L.. -L../lib -lsilc +silcd_DEPENDENCIES = ../lib/libsilc.a + +LIBS = $(SILC_COMMON_LIBS) +LDADD = EXTRA_DIST = *.h -INCLUDES = -I. -I.. -I../lib/silccore -I../lib/silccrypt \ - -I../lib/silcmath -I../lib/silcske -I../lib/silcsim \ - -I../includes \ - -I../lib/silcmath/gmp-3.0.1 +include $(top_srcdir)/Makefile.defines.in diff --git a/apps/silcd/command.c b/apps/silcd/command.c index b9a0cc63..5c42734f 100644 --- a/apps/silcd/command.c +++ b/apps/silcd/command.c @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -17,927 +17,5286 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "serverincludes.h" #include "server_internal.h" +static int silc_server_is_registered(SilcServer server, + SilcSocketConnection sock, + SilcServerCommandContext cmd, + SilcCommand command); +static void +silc_server_command_send_status_reply(SilcServerCommandContext cmd, + SilcCommand command, + SilcCommandStatus status); +static void +silc_server_command_send_status_data(SilcServerCommandContext cmd, + SilcCommand command, + SilcCommandStatus status, + uint32 arg_type, + unsigned char *arg, + uint32 arg_len); +static bool +silc_server_command_pending_error_check(SilcServerCommandContext cmd, + SilcServerCommandReplyContext cmdr, + SilcCommand command); +SILC_TASK_CALLBACK(silc_server_command_process_timeout); + /* Server command list. */ SilcServerCommand silc_command_list[] = { SILC_SERVER_CMD(whois, WHOIS, SILC_CF_LAG | SILC_CF_REG), SILC_SERVER_CMD(whowas, WHOWAS, SILC_CF_LAG | SILC_CF_REG), SILC_SERVER_CMD(identify, IDENTIFY, SILC_CF_LAG | SILC_CF_REG), - SILC_SERVER_CMD(nick, NICK, SILC_CF_LAG | SILC_CF_REG), - SILC_SERVER_CMD(list, LIST, SILC_CF_LAG | SILC_CF_REG), + SILC_SERVER_CMD(nick, NICK, SILC_CF_LAG_STRICT | SILC_CF_REG), + SILC_SERVER_CMD(list, LIST, SILC_CF_LAG_STRICT | SILC_CF_REG), SILC_SERVER_CMD(topic, TOPIC, SILC_CF_LAG | SILC_CF_REG), SILC_SERVER_CMD(invite, INVITE, SILC_CF_LAG | SILC_CF_REG), SILC_SERVER_CMD(quit, QUIT, SILC_CF_LAG | SILC_CF_REG), - SILC_SERVER_CMD(kill, KILL, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER), + SILC_SERVER_CMD(kill, KILL, SILC_CF_LAG_STRICT | SILC_CF_REG | SILC_CF_OPER), SILC_SERVER_CMD(info, INFO, SILC_CF_LAG | SILC_CF_REG), SILC_SERVER_CMD(connect, CONNECT, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER), SILC_SERVER_CMD(ping, PING, SILC_CF_LAG | SILC_CF_REG), SILC_SERVER_CMD(oper, OPER, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER), - SILC_SERVER_CMD(join, JOIN, SILC_CF_LAG | SILC_CF_REG), + SILC_SERVER_CMD(join, JOIN, SILC_CF_LAG_STRICT | SILC_CF_REG), SILC_SERVER_CMD(motd, MOTD, SILC_CF_LAG | SILC_CF_REG), SILC_SERVER_CMD(umode, UMODE, SILC_CF_LAG | SILC_CF_REG), - SILC_SERVER_CMD(cmode, CMODE, SILC_CF_LAG | SILC_CF_REG), - SILC_SERVER_CMD(kick, KICK, SILC_CF_LAG | SILC_CF_REG), - SILC_SERVER_CMD(restart, RESTART, - SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER), + SILC_SERVER_CMD(cmode, CMODE, SILC_CF_LAG_STRICT | SILC_CF_REG), + SILC_SERVER_CMD(cumode, CUMODE, SILC_CF_LAG | SILC_CF_REG), + SILC_SERVER_CMD(kick, KICK, SILC_CF_LAG_STRICT | SILC_CF_REG), + SILC_SERVER_CMD(ban, BAN, SILC_CF_LAG_STRICT | SILC_CF_REG), SILC_SERVER_CMD(close, CLOSE, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER), - SILC_SERVER_CMD(die, DIE, SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER), + SILC_SERVER_CMD(shutdown, SHUTDOWN, SILC_CF_LAG | SILC_CF_REG | + SILC_CF_OPER), SILC_SERVER_CMD(silcoper, SILCOPER, SILC_CF_LAG | SILC_CF_REG | SILC_CF_SILC_OPER), - SILC_SERVER_CMD(leave, LEAVE, SILC_CF_LAG | SILC_CF_REG), - SILC_SERVER_CMD(names, NAMES, SILC_CF_LAG | SILC_CF_REG), + SILC_SERVER_CMD(leave, LEAVE, SILC_CF_LAG_STRICT | SILC_CF_REG), + SILC_SERVER_CMD(users, USERS, SILC_CF_LAG | SILC_CF_REG), + SILC_SERVER_CMD(getkey, GETKEY, SILC_CF_LAG | SILC_CF_REG), { NULL, 0 }, }; -/* List of pending commands. */ -SilcServerCommandPending *silc_command_pending = NULL; +/* Performs several checks to the command. It first checks whether this + command was called as pending command callback. If it was then it checks + whether error occurred in the command reply where the pending command + callback was called. + + It also checks that the requested command includes correct amount + of arguments. */ +#define SILC_SERVER_COMMAND_CHECK(command, context, min, max) \ +do { \ + uint32 _argc; \ + \ + SILC_LOG_DEBUG(("Start")); \ + \ + if (silc_server_command_pending_error_check(cmd, context2, command)) { \ + silc_server_command_free(cmd); \ + return; \ + } \ + \ + _argc = silc_argument_get_arg_num(cmd->args); \ + if (_argc < min) { \ + silc_server_command_send_status_reply(cmd, command, \ + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); \ + silc_server_command_free(cmd); \ + return; \ + } \ + if (_argc > max) { \ + silc_server_command_send_status_reply(cmd, command, \ + SILC_STATUS_ERR_TOO_MANY_PARAMS); \ + silc_server_command_free(cmd); \ + return; \ + } \ +} while(0) + +/* Returns TRUE if the connection is registered. Unregistered connections + usually cannot send commands hence the check. */ + +static int silc_server_is_registered(SilcServer server, + SilcSocketConnection sock, + SilcServerCommandContext cmd, + SilcCommand command) +{ + SilcIDListData idata = (SilcIDListData)sock->user_data; + + if (!idata) + return FALSE; + + if (idata->status & SILC_IDLIST_STATUS_REGISTERED) + return TRUE; + + silc_server_command_send_status_reply(cmd, command, + SILC_STATUS_ERR_NOT_REGISTERED); + silc_server_command_free(cmd); + return FALSE; +} + +/* Internal context to hold data when executed command with timeout. */ +typedef struct { + SilcServerCommandContext ctx; + SilcServerCommand *cmd; +} *SilcServerCommandTimeout; + +/* Timeout callback to process commands with timeout for client. Client's + commands are always executed with timeout. */ + +SILC_TASK_CALLBACK(silc_server_command_process_timeout) +{ + SilcServerCommandTimeout timeout = (SilcServerCommandTimeout)context; + SilcClientEntry client = (SilcClientEntry)timeout->ctx->sock->user_data; + + /* Update access time */ + client->last_command = time(NULL); + + if (!(timeout->cmd->flags & SILC_CF_REG)) + timeout->cmd->cb(timeout->ctx, NULL); + else if (silc_server_is_registered(timeout->ctx->server, + timeout->ctx->sock, + timeout->ctx, + timeout->cmd->cmd)) + timeout->cmd->cb(timeout->ctx, NULL); + + silc_free(timeout); +} + +/* Processes received command packet. */ + +void silc_server_command_process(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcServerCommandContext ctx; + SilcServerCommand *cmd; + SilcCommand command; + + /* Allocate command context. This must be free'd by the + command routine receiving it. */ + ctx = silc_server_command_alloc(); + ctx->server = server; + ctx->sock = silc_socket_dup(sock); + ctx->packet = silc_packet_context_dup(packet); /* Save original packet */ + + /* Parse the command payload in the packet */ + ctx->payload = silc_command_payload_parse(packet->buffer); + if (!ctx->payload) { + SILC_LOG_ERROR(("Bad command payload, packet dropped")); + silc_buffer_free(packet->buffer); + silc_packet_context_free(packet); + silc_socket_free(ctx->sock); + silc_free(ctx); + return; + } + ctx->args = silc_command_get_args(ctx->payload); + + /* Get the command */ + command = silc_command_get(ctx->payload); + for (cmd = silc_command_list; cmd->cb; cmd++) + if (cmd->cmd == command) + break; + + if (cmd == NULL) { + silc_server_command_send_status_reply(ctx, command, + SILC_STATUS_ERR_UNKNOWN_COMMAND); + silc_server_command_free(ctx); + return; + } + + /* Execute client's commands always with timeout. Normally they are + executed with zero (0) timeout but if client is sending command more + frequently than once in 2 seconds, then the timeout may be 0 to 2 + seconds. */ + if (sock->type == SILC_SOCKET_TYPE_CLIENT) { + SilcClientEntry client = (SilcClientEntry)sock->user_data; + SilcServerCommandTimeout timeout = silc_calloc(1, sizeof(*timeout)); + int fast; + + timeout->ctx = ctx; + timeout->cmd = cmd; + + if (client->last_command && (time(NULL) - client->last_command) < 2) { + client->fast_command++; + fast = FALSE; + } else { + client->fast_command = ((client->fast_command - 1) <= 0 ? 0 : + client->fast_command--); + fast = TRUE; + } + + if (!fast && ((cmd->flags & SILC_CF_LAG_STRICT) || + (client->fast_command > 5 && cmd->flags & SILC_CF_LAG))) + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_command_process_timeout, + (void *)timeout, + 2 - (time(NULL) - client->last_command), 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); + else + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_command_process_timeout, + (void *)timeout, + 0, 1, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); + return; + } + + /* Execute for server */ + + if (!(cmd->flags & SILC_CF_REG)) + cmd->cb(ctx, NULL); + else if (silc_server_is_registered(server, sock, ctx, cmd->cmd)) + cmd->cb(ctx, NULL); +} + +/* Allocate Command Context */ + +SilcServerCommandContext silc_server_command_alloc() +{ + SilcServerCommandContext ctx = silc_calloc(1, sizeof(*ctx)); + ctx->users++; + return ctx; +} + +/* Free's the command context allocated before executing the command */ + +void silc_server_command_free(SilcServerCommandContext ctx) +{ + ctx->users--; + SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users + 1, + ctx->users)); + if (ctx->users < 1) { + if (ctx->payload) + silc_command_payload_free(ctx->payload); + if (ctx->packet) + silc_packet_context_free(ctx->packet); + if (ctx->sock) + silc_socket_free(ctx->sock); /* Decrease reference counter */ + silc_free(ctx); + } +} -/* Add new pending command to the list of pending commands. Currently - pending commands are executed from command replies, thus we can - execute any command after receiving some specific command reply. +/* Duplicate Command Context by adding reference counter. The context won't + be free'd untill it hits zero. */ - The argument `reply_cmd' is the command reply from where the callback - function is to be called, thus, it IS NOT the command to be executed. */ +SilcServerCommandContext +silc_server_command_dup(SilcServerCommandContext ctx) +{ + ctx->users++; + SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users - 1, + ctx->users)); + return ctx; +} -void silc_server_command_pending(SilcCommand reply_cmd, +/* Add new pending command to be executed when reply to a command has been + received. The `reply_cmd' is the command that will call the `callback' + with `context' when reply has been received. It can be SILC_COMMAND_NONE + to match any command with the `ident'. If `ident' is non-zero + the `callback' will be executed when received reply with command + identifier `ident'. */ + +void silc_server_command_pending(SilcServer server, + SilcCommand reply_cmd, + uint16 ident, + SilcServerPendingDestructor destructor, SilcCommandCb callback, void *context) { - SilcServerCommandPending *reply, *r; + SilcServerCommandPending *reply; reply = silc_calloc(1, sizeof(*reply)); reply->reply_cmd = reply_cmd; + reply->ident = ident; reply->context = context; reply->callback = callback; + reply->destructor = destructor; + silc_dlist_add(server->pending_commands, reply); +} - if (silc_command_pending == NULL) { - silc_command_pending = reply; - return; - } +/* Deletes pending command by reply command type. */ + +void silc_server_command_pending_del(SilcServer server, + SilcCommand reply_cmd, + uint16 ident) +{ + SilcServerCommandPending *r; - for (r = silc_command_pending; r; r = r->next) { - if (r->next == NULL) { - r->next = reply; + silc_dlist_start(server->pending_commands); + while ((r = silc_dlist_get(server->pending_commands)) != SILC_LIST_END) { + if (r->reply_cmd == reply_cmd && r->ident == ident) { + silc_dlist_del(server->pending_commands, r); break; } } } -/* Deletes pending command by reply command type. */ +/* Checks for pending commands and marks callbacks to be called from + the command reply function. Returns TRUE if there were pending command. */ -void silc_server_command_pending_del(SilcCommand reply_cmd) +SilcServerCommandPendingCallbacks +silc_server_command_pending_check(SilcServer server, + SilcServerCommandReplyContext ctx, + SilcCommand command, + uint16 ident, + uint32 *callbacks_count) { - SilcServerCommandPending *r, *tmp; - - if (silc_command_pending) { - if (silc_command_pending->reply_cmd == reply_cmd) { - silc_free(silc_command_pending); - silc_command_pending = NULL; - return; - } - - for (r = silc_command_pending; r; r = r->next) { - if (r->next && r->next->reply_cmd == reply_cmd) { - tmp = r->next; - r->next = r->next->next; - silc_free(tmp); - break; - } + SilcServerCommandPending *r; + SilcServerCommandPendingCallbacks callbacks = NULL; + int i = 0; + + silc_dlist_start(server->pending_commands); + while ((r = silc_dlist_get(server->pending_commands)) != SILC_LIST_END) { + if ((r->reply_cmd == command || r->reply_cmd == SILC_COMMAND_NONE) + && r->ident == ident) { + callbacks = silc_realloc(callbacks, sizeof(*callbacks) * (i + 1)); + callbacks[i].context = r->context; + callbacks[i].callback = r->callback; + callbacks[i].destructor = r->destructor; + ctx->ident = ident; + i++; } } + + *callbacks_count = i; + return callbacks; } -/* Free's the command context allocated before executing the command */ +/* Destructor function for pending callbacks. This is called when using + pending commands to free the context given for the pending command. */ -static void silc_server_command_free(SilcServerCommandContext cmd) +static void silc_server_command_destructor(void *context) { - if (cmd) { - silc_command_free_payload(cmd->payload); - silc_free(cmd); - } + silc_server_command_free((SilcServerCommandContext)context); } -/* Sends command status message as command reply packet. */ +/* Sends simple status message as command reply packet */ static void -silc_server_command_send_status_msg(SilcServerCommandContext cmd, - SilcCommand command, - SilcCommandStatus status, - unsigned char *msg, - unsigned int msg_len) +silc_server_command_send_status_reply(SilcServerCommandContext cmd, + SilcCommand command, + SilcCommandStatus status) { - SilcBuffer sp_buf, buffer; + SilcBuffer buffer; SILC_LOG_DEBUG(("Sending command status %d", status)); - sp_buf = silc_command_encode_status_payload(status, msg, msg_len); - buffer = silc_command_encode_payload_va(command, 1, - sp_buf->data, sp_buf->len); + buffer = + silc_command_reply_payload_encode_va(command, status, + silc_command_get_ident(cmd->payload), + 0); silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, buffer->data, buffer->len, FALSE); silc_buffer_free(buffer); - silc_buffer_free(sp_buf); } -/* Sends simple status message as command reply packet */ +/* Sends command status reply with one extra argument. The argument + type must be sent as argument. */ static void -silc_server_command_send_status_reply(SilcServerCommandContext cmd, - SilcCommand command, - SilcCommandStatus status) +silc_server_command_send_status_data(SilcServerCommandContext cmd, + SilcCommand command, + SilcCommandStatus status, + uint32 arg_type, + unsigned char *arg, + uint32 arg_len) { - SilcBuffer sp_buf, buffer; + SilcBuffer buffer; SILC_LOG_DEBUG(("Sending command status %d", status)); - sp_buf = silc_command_encode_status_payload(status, NULL, 0); - buffer = silc_command_encode_payload_va(command, 1, - sp_buf->data, sp_buf->len); + buffer = + silc_command_reply_payload_encode_va(command, status, + silc_command_get_ident(cmd->payload), + 1, arg_type, arg, arg_len); silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, buffer->data, buffer->len, FALSE); silc_buffer_free(buffer); - silc_buffer_free(sp_buf); } -/* Server side of command WHOIS. Processes user's query and sends found - results as command replies back to the client. */ +/* This function can be called to check whether in the command reply + an error occurred. This function has no effect if this is called + when the command function was not called as pending command callback. + This returns TRUE if error had occurred. */ -SILC_SERVER_CMD_FUNC(whois) +static bool +silc_server_command_pending_error_check(SilcServerCommandContext cmd, + SilcServerCommandReplyContext cmdr, + SilcCommand command) { - SilcServerCommandContext cmd = (SilcServerCommandContext)context; - char *tmp, *nick = NULL, *server = NULL; - unsigned int argc, count = 0, len; - SilcClientList *entry; - SilcBuffer sp_buf, packet; - unsigned char *id_string; + SilcCommandStatus status; + + if (!cmd->pending || !cmdr) + return FALSE; + + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmdr->args, 1, NULL)); + if (status != SILC_STATUS_OK && + status != SILC_STATUS_LIST_START && + status != SILC_STATUS_LIST_ITEM && + status != SILC_STATUS_LIST_END) { + /* Send the error message */ + silc_server_command_send_status_reply(cmd, command, status); + return TRUE; + } - SILC_LOG_DEBUG(("Start")); + return FALSE; +} - argc = silc_command_get_arg_num(cmd->payload); - if (argc < 1) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS, - SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); - goto out; - } - if (argc > 2) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS, - SILC_STATUS_ERR_TOO_MANY_PARAMS); - goto out; - } +/****************************************************************************** - /* Get the nickname@server string and parse it. */ - tmp = silc_command_get_first_arg(cmd->payload, NULL); - if (tmp) { - if (strchr(tmp, '@')) { - len = strcspn(tmp, "@"); - nick = silc_calloc(len + 1, sizeof(char)); - memcpy(nick, tmp, len); - server = silc_calloc(strlen(tmp) - len, sizeof(char)); - memcpy(server, tmp + len + 1, strlen(tmp) - len - 1); + WHOIS Functions + +******************************************************************************/ + +static int +silc_server_command_whois_parse(SilcServerCommandContext cmd, + SilcClientID ***client_id, + uint32 *client_id_count, + char **nickname, + char **server_name, + int *count, + SilcCommand command) +{ + unsigned char *tmp; + uint32 len; + uint32 argc = silc_argument_get_arg_num(cmd->args); + int i, k; + + /* If client ID is in the command it must be used instead of nickname */ + tmp = silc_argument_get_arg_type(cmd->args, 3, &len); + if (!tmp) { + /* No ID, get the nickname@server string and parse it. */ + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + if (tmp) { + silc_parse_userfqdn(tmp, nickname, server_name); } else { - nick = strdup(tmp); + silc_server_command_send_status_reply(cmd, command, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + return FALSE; } } else { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS, - SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); - goto out; + /* Command includes ID, we must use that. Also check whether the command + has more than one ID set - take them all. */ + + *client_id = silc_calloc(1, sizeof(**client_id)); + (*client_id)[0] = silc_id_payload_parse_id(tmp, len); + if ((*client_id)[0] == NULL) { + silc_free(*client_id); + return FALSE; + } + *client_id_count = 1; + + /* Take all ID's from the command packet */ + if (argc > 1) { + for (k = 1, i = 1; i < argc; i++) { + tmp = silc_argument_get_arg_type(cmd->args, i + 3, &len); + if (tmp) { + *client_id = silc_realloc(*client_id, sizeof(**client_id) * + (*client_id_count + 1)); + (*client_id)[k] = silc_id_payload_parse_id(tmp, len); + if ((*client_id)[k] == NULL) { + /* Cleanup all and fail */ + for (i = 0; i < *client_id_count; i++) + silc_free((*client_id)[i]); + silc_free(*client_id); + return FALSE; + } + (*client_id_count)++; + k++; + } + } + } } /* Get the max count of reply messages allowed */ - if (argc == 2) { - tmp = silc_command_get_next_arg(cmd->payload, NULL); - if (!tmp) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOIS, - SILC_STATUS_ERR_TOO_MANY_PARAMS); - if (nick) - silc_free(nick); - if (server) - silc_free(server); - goto out; + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (tmp) + *count = atoi(tmp); + else + *count = 0; + + return TRUE; +} + +/* Resolve context used by both WHOIS and IDENTIFY commands */ +typedef struct { + SilcServerEntry router; + uint16 ident; + unsigned char **res_argv; + uint32 *res_argv_lens; + uint32 *res_argv_types; + uint32 res_argc; +} *SilcServerResolveContext; + +static bool +silc_server_command_whois_check(SilcServerCommandContext cmd, + SilcClientEntry *clients, + uint32 clients_count) +{ + SilcServer server = cmd->server; + SilcClientEntry entry; + SilcServerResolveContext resolve = NULL, r = NULL; + uint32 resolve_count = 0; + int i, k; + bool no_res = TRUE; + + for (i = 0; i < clients_count; i++) { + entry = clients[i]; + + if (!entry || (entry->nickname && entry->username && entry->userinfo) || + !(entry->data.status & SILC_IDLIST_STATUS_REGISTERED) || + !entry->router) + continue; + + /* We need to resolve this entry since it is not complete */ + + if (!cmd->pending && entry->data.status & SILC_IDLIST_STATUS_RESOLVING) { + /* The entry is being resolved (and we are not the resolver) so attach + to the command reply and we're done with this one. */ + silc_server_command_pending(server, SILC_COMMAND_NONE, + entry->resolve_cmd_ident, + silc_server_command_destructor, + silc_server_command_whois, + silc_server_command_dup(cmd)); + no_res = FALSE; + } else { + if (entry->data.status & SILC_IDLIST_STATUS_RESOLVING) { + /* We've resolved this and it still is not ready. We'll return + and are that this will be handled again after it is resolved. */ + for (i = 0; i < resolve_count; i++) { + for (k = 0; k < r->res_argc; k++) + silc_free(r->res_argv[k]); + silc_free(r->res_argv); + silc_free(r->res_argv_lens); + silc_free(r->res_argv_types); + } + silc_free(resolve); + return FALSE; + } else { + /* We'll resolve this client */ + SilcBuffer idp; + + r = NULL; + for (k = 0; k < resolve_count; k++) { + if (resolve[k].router == entry->router) { + r = &resolve[k]; + break; + } + } + + if (!r) { + resolve = silc_realloc(resolve, sizeof(*resolve) * + (resolve_count + 1)); + r = &resolve[resolve_count]; + memset(r, 0, sizeof(*r)); + r->router = entry->router; + r->ident = ++server->cmd_ident; + resolve_count++; + } + + r->res_argv = silc_realloc(r->res_argv, sizeof(*r->res_argv) * + (r->res_argc + 1)); + r->res_argv_lens = silc_realloc(r->res_argv_lens, + sizeof(*r->res_argv_lens) * + (r->res_argc + 1)); + r->res_argv_types = silc_realloc(r->res_argv_types, + sizeof(*r->res_argv_types) * + (r->res_argc + 1)); + idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + r->res_argv[r->res_argc] = silc_calloc(idp->len, + sizeof(**r->res_argv)); + memcpy(r->res_argv[r->res_argc], idp->data, idp->len); + r->res_argv_lens[r->res_argc] = idp->len; + r->res_argv_types[r->res_argc] = r->res_argc + 3; + r->res_argc++; + silc_buffer_free(idp); + + entry->resolve_cmd_ident = r->ident; + entry->data.status |= SILC_IDLIST_STATUS_RESOLVING; + entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVED; + } } - count = atoi(tmp); } - /* Then, make the query from our local client list */ - entry = silc_idlist_find_client_by_nickname(cmd->server->local_list->clients, - nick, server); - if (!entry) { + /* Do the resolving */ + for (i = 0; i < resolve_count; i++) { + SilcBuffer res_cmd; + + r = &resolve[i]; + + /* Send WHOIS request. We send WHOIS since we're doing the requesting + now anyway so make it a good one. */ + res_cmd = silc_command_payload_encode(SILC_COMMAND_WHOIS, + r->res_argc, r->res_argv, + r->res_argv_lens, + r->res_argv_types, + r->ident); + silc_server_packet_send(server, r->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + res_cmd->data, res_cmd->len, FALSE); + + /* Reprocess this packet after received reply */ + silc_server_command_pending(server, SILC_COMMAND_WHOIS, + r->ident, + silc_server_command_destructor, + silc_server_command_whois, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_buffer_free(res_cmd); + for (k = 0; k < r->res_argc; k++) + silc_free(r->res_argv[k]); + silc_free(r->res_argv); + silc_free(r->res_argv_lens); + silc_free(r->res_argv_types); + no_res = FALSE; + } + silc_free(resolve); - /* If we are normal server and are connected to a router we will - make global query from the router. */ - if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone) { + return no_res; +} - goto ok; +static void +silc_server_command_whois_send_reply(SilcServerCommandContext cmd, + SilcClientEntry *clients, + uint32 clients_count, + int count) +{ + SilcServer server = cmd->server; + char *tmp; + int i, k, len; + SilcBuffer packet, idp, channels; + SilcClientEntry entry; + SilcCommandStatus status; + uint16 ident = silc_command_get_ident(cmd->payload); + char nh[256], uh[256]; + unsigned char idle[4], mode[4]; + SilcSocketConnection hsock; + + len = 0; + for (i = 0; i < clients_count; i++) + if (clients[i]->data.status & SILC_IDLIST_STATUS_REGISTERED) + len++; + + if (len == 0 && clients_count) { + entry = clients[0]; + if (entry->nickname) { + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS, + SILC_STATUS_ERR_NO_SUCH_NICK, + 3, entry->nickname, + strlen(entry->nickname)); + } else { + SilcBuffer idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID, + 2, idp->data, idp->len); + silc_buffer_free(idp); } - - /* If we are router then we will check our global list as well. */ - if (cmd->server->server_type == SILC_ROUTER) { - entry = - silc_idlist_find_client_by_nickname(cmd->server->global_list->clients, - nick, server); - if (!entry) { - silc_server_command_send_status_msg(cmd, SILC_COMMAND_WHOIS, - SILC_STATUS_ERR_NO_SUCH_NICK, - tmp, strlen(tmp)); - goto out; + + return; + } + + status = SILC_STATUS_OK; + if (len > 1) + status = SILC_STATUS_LIST_START; + + for (i = 0, k = 0; i < clients_count; i++) { + entry = clients[i]; + + if (!(entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) { + if (clients_count == 1) { + if (entry->nickname) { + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS, + SILC_STATUS_ERR_NO_SUCH_NICK, + 3, entry->nickname, + strlen(entry->nickname)); + } else { + SilcBuffer idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID, + 2, idp->data, idp->len); + silc_buffer_free(idp); + } } - goto ok; + continue; } - silc_server_command_send_status_msg(cmd, SILC_COMMAND_WHOIS, - SILC_STATUS_ERR_NO_SUCH_NICK, - tmp, strlen(tmp)); - goto out; - } + if (k >= 1) + status = SILC_STATUS_LIST_ITEM; - ok: - /* XXX, works only for local server info */ + if (clients_count > 1 && k == clients_count - 1) + status = SILC_STATUS_LIST_END; - /* Send WHOIS reply */ - id_string = silc_id_id2str(entry->id, SILC_ID_CLIENT); - tmp = silc_command_get_first_arg(cmd->payload, NULL), - sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0); + if (count && k - 1 == count) + status = SILC_STATUS_LIST_END; - /* XXX */ - if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) { - char nh[256], uh[256]; - SilcSocketConnection hsock; + if (count && k - 1 > count) + break; + /* Sanity check, however these should never fail. However, as + this sanity check has been added here they have failed. */ + if (!entry->nickname || !entry->username || !entry->userinfo) + continue; + + /* Send WHOIS reply */ + idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + tmp = silc_argument_get_first_arg(cmd->args, NULL); + memset(uh, 0, sizeof(uh)); memset(nh, 0, sizeof(nh)); - + memset(idle, 0, sizeof(idle)); + strncat(nh, entry->nickname, strlen(entry->nickname)); - strncat(nh, "@", 1); - len = entry->router ? strlen(entry->router->server_name) : - strlen(cmd->server->server_name); - strncat(nh, entry->router ? entry->router->server_name : - cmd->server->server_name, len); - + if (!strchr(entry->nickname, '@')) { + strncat(nh, "@", 1); + if (entry->servername) { + strncat(nh, entry->servername, strlen(entry->servername)); + } else { + len = entry->router ? strlen(entry->router->server_name) : + strlen(server->server_name); + strncat(nh, entry->router ? entry->router->server_name : + server->server_name, len); + } + } + strncat(uh, entry->username, strlen(entry->username)); - strncat(uh, "@", 1); - hsock = (SilcSocketConnection)entry->connection; - len = hsock->hostname ? strlen(hsock->hostname) : strlen(hsock->ip); - strncat(uh, hsock->hostname ? hsock->hostname : hsock->ip, len); - - /* XXX */ - if (entry->userinfo) - packet = - silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 5, - sp_buf->data, sp_buf->len, - id_string, SILC_ID_CLIENT_LEN, - nh, strlen(nh), - uh, strlen(uh), - entry->userinfo, - strlen(entry->userinfo)); - else - packet = - silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 4, - sp_buf->data, sp_buf->len, - id_string, SILC_ID_CLIENT_LEN, - nh, strlen(nh), - uh, strlen(uh)); + if (!strchr(entry->username, '@')) { + strncat(uh, "@", 1); + hsock = (SilcSocketConnection)entry->connection; + len = strlen(hsock->hostname); + strncat(uh, hsock->hostname, len); + } - } else { - /* XXX */ - packet = - silc_command_encode_payload_va(SILC_COMMAND_WHOIS, 4, - sp_buf->data, sp_buf->len, - id_string, SILC_ID_CLIENT_LEN, - entry->nickname, strlen(entry->nickname), - tmp, strlen(tmp)); /* XXX */ - } - silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY, - 0, packet->data, packet->len, FALSE); + channels = silc_server_get_client_channel_list(server, entry); + + SILC_PUT32_MSB(entry->mode, mode); - silc_free(id_string); - silc_buffer_free(packet); - silc_free(sp_buf); + if (entry->connection) { + SILC_PUT32_MSB((time(NULL) - entry->data.last_receive), idle); + } - out: - silc_server_command_free(cmd); -} + if (channels) + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_WHOIS, + status, ident, 7, + 2, idp->data, idp->len, + 3, nh, strlen(nh), + 4, uh, strlen(uh), + 5, entry->userinfo, + strlen(entry->userinfo), + 6, channels->data, + channels->len, + 7, mode, 4, + 8, idle, 4); + else + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_WHOIS, + status, ident, 6, + 2, idp->data, idp->len, + 3, nh, strlen(nh), + 4, uh, strlen(uh), + 5, entry->userinfo, + strlen(entry->userinfo), + 7, mode, 4, + 8, idle, 4); + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, + 0, packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(idp); + if (channels) + silc_buffer_free(channels); -SILC_SERVER_CMD_FUNC(whowas) -{ + k++; + } } -SILC_SERVER_CMD_FUNC(identify) +static int +silc_server_command_whois_process(SilcServerCommandContext cmd) { - SilcServerCommandContext cmd = (SilcServerCommandContext)context; - char *tmp, *nick = NULL, *server = NULL; - unsigned int argc, count = 0, len; - SilcClientList *entry; - SilcBuffer sp_buf, packet; - unsigned char *id_string; - - SILC_LOG_DEBUG(("Start")); - - argc = silc_command_get_arg_num(cmd->payload); - if (argc < 1) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY, - SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); - goto out; - } - if (argc > 2) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY, - SILC_STATUS_ERR_TOO_MANY_PARAMS); + SilcServer server = cmd->server; + char *nick = NULL, *server_name = NULL; + int count = 0; + SilcClientEntry *clients = NULL, entry; + SilcClientID **client_id = NULL; + uint32 client_id_count = 0, clients_count = 0; + int i, ret = 0; + bool check_global = FALSE; + + /* Protocol dictates that we must always send the received WHOIS request + to our router if we are normal server, so let's do it now unless we + are standalone. We will not send any replies to the client until we + have received reply from the router. */ + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT && + server->server_type == SILC_SERVER && !cmd->pending && + !server->standalone) { + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + /* Send WHOIS command to our router */ + silc_server_packet_send(server, (SilcSocketConnection) + server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_WHOIS, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_whois, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_command_set_ident(cmd->payload, old_ident); + + silc_buffer_free(tmpbuf); + ret = -1; goto out; } - /* Get the nickname@server string and parse it. */ - tmp = silc_command_get_first_arg(cmd->payload, NULL); - if (tmp) { - if (strchr(tmp, '@')) { - len = strcspn(tmp, "@"); - nick = silc_calloc(len + 1, sizeof(char)); - memcpy(nick, tmp, len); - server = silc_calloc(strlen(tmp) - len, sizeof(char)); - memcpy(server, tmp + len + 1, strlen(tmp) - len - 1); - } else { - nick = strdup(tmp); + /* We are ready to process the command request. Let's search for the + requested client and send reply to the requesting client. */ + + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) + check_global = TRUE; + else if (server->server_type != SILC_SERVER) + check_global = TRUE; + + /* Parse the whois request */ + if (!silc_server_command_whois_parse(cmd, &client_id, &client_id_count, + &nick, &server_name, &count, + SILC_COMMAND_WHOIS)) + return 0; + + /* Get all clients matching that ID or nickname from local list */ + if (client_id_count) { + /* Check all Client ID's received in the command packet */ + for (i = 0; i < client_id_count; i++) { + entry = silc_idlist_find_client_by_id(server->local_list, + client_id[i], TRUE, NULL); + if (!entry && check_global) + entry = silc_idlist_find_client_by_id(server->global_list, + client_id[i], TRUE, NULL); + if (entry) { + clients = silc_realloc(clients, sizeof(*clients) * + (clients_count + 1)); + clients[clients_count++] = entry; + } } } else { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY, - SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); - goto out; - } - - /* Get the max count of reply messages allowed */ - if (argc == 2) { - tmp = silc_command_get_next_arg(cmd->payload, NULL); - if (!tmp) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY, - SILC_STATUS_ERR_TOO_MANY_PARAMS); - goto out; + if (!silc_idlist_get_clients_by_hash(server->local_list, + nick, server->md5hash, + &clients, &clients_count)) + silc_idlist_get_clients_by_nickname(server->local_list, + nick, server_name, + &clients, &clients_count); + if (check_global) { + if (!silc_idlist_get_clients_by_hash(server->global_list, + nick, server->md5hash, + &clients, &clients_count)) + silc_idlist_get_clients_by_nickname(server->global_list, + nick, server_name, + &clients, &clients_count); } - count = atoi(tmp); } - - /* Then, make the query from our local client list */ - entry = silc_idlist_find_client_by_hash(cmd->server->local_list->clients, - nick, cmd->server->md5hash); - if (!entry) { - - /* If we are normal server and are connected to a router we will - make global query from the router. */ - if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone) { - SilcBuffer buffer = cmd->packet->buffer; - - /* Forward the received IDENTIFY command to our router */ - silc_buffer_push(buffer, buffer->data - buffer->head); - silc_server_packet_forward(cmd->server, (SilcSocketConnection) - cmd->server->id_entry->router->connection, - buffer->data, buffer->len, - TRUE); - goto out; - } - - /* If we are router then we will check our global list as well. */ - if (cmd->server->server_type == SILC_ROUTER) { - entry = - silc_idlist_find_client_by_hash(cmd->server->global_list->clients, - nick, cmd->server->md5hash); - if (!entry) { - silc_server_command_send_status_msg(cmd, SILC_COMMAND_IDENTIFY, - SILC_STATUS_ERR_NO_SUCH_NICK, - tmp, strlen(tmp)); - goto out; - } - goto ok; + + if (!clients) { + /* Such client(s) really does not exist in the SILC network. */ + if (!client_id_count) { + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS, + SILC_STATUS_ERR_NO_SUCH_NICK, + 3, nick, strlen(nick)); + } else { + SilcBuffer idp = silc_id_payload_encode(client_id[0], SILC_ID_CLIENT); + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOIS, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID, + 2, idp->data, idp->len); + silc_buffer_free(idp); } - - silc_server_command_send_status_msg(cmd, SILC_COMMAND_IDENTIFY, - SILC_STATUS_ERR_NO_SUCH_NICK, - tmp, strlen(tmp)); goto out; } - ok: - /* Send IDENTIFY reply */ - id_string = silc_id_id2str(entry->id, SILC_ID_CLIENT); - tmp = silc_command_get_first_arg(cmd->payload, NULL); - sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0); - packet = silc_command_encode_payload_va(SILC_COMMAND_IDENTIFY, 3, - sp_buf->data, sp_buf->len, - id_string, SILC_ID_CLIENT_LEN, - nick, strlen(nick)); - if (cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED) { - void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type); - silc_server_packet_send_dest(cmd->server, cmd->sock, - SILC_PACKET_COMMAND_REPLY, 0, - id, cmd->packet->src_id_type, - packet->data, packet->len, FALSE); - silc_free(id); - } else - silc_server_packet_send(cmd->server, cmd->sock, - SILC_PACKET_COMMAND_REPLY, 0, - packet->data, packet->len, FALSE); + /* Router always finds the client entry if it exists in the SILC network. + However, it might be incomplete entry and does not include all the + mandatory fields that WHOIS command reply requires. Check for these and + make query from the server who owns the client if some fields are + missing. */ + if (!silc_server_command_whois_check(cmd, clients, clients_count)) { + ret = -1; + goto out; + } - silc_free(id_string); - silc_buffer_free(packet); - silc_free(sp_buf); + /* Send the command reply */ + silc_server_command_whois_send_reply(cmd, clients, clients_count, + count); out: - if (nick) - silc_free(nick); - if (server) - silc_free(server); - silc_server_command_free(cmd); + if (client_id_count) { + for (i = 0; i < client_id_count; i++) + silc_free(client_id[i]); + silc_free(client_id); + } + silc_free(clients); + silc_free(nick); + silc_free(server_name); + + return ret; } -/* Checks string for bad characters and returns TRUE if they are found. */ +/* Server side of command WHOIS. Processes user's query and sends found + results as command replies back to the client. */ -static int silc_server_command_bad_chars(char *nick) +SILC_SERVER_CMD_FUNC(whois) { - if (strchr(nick, '\\')) return TRUE; - if (strchr(nick, '\"')) return TRUE; - if (strchr(nick, '´')) return TRUE; - if (strchr(nick, '`')) return TRUE; - if (strchr(nick, '\'')) return TRUE; - if (strchr(nick, '*')) return TRUE; - if (strchr(nick, '/')) return TRUE; - if (strchr(nick, '@')) return TRUE; + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + int ret = 0; - return FALSE; + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_WHOIS, cmd, 1, 3328); + + ret = silc_server_command_whois_process(cmd); + + if (!ret) + silc_server_command_free(cmd); } -/* Server side of command NICK. Sets nickname for user. Setting - nickname causes generation of a new client ID for the client. The - new client ID is sent to the client after changing the nickname. */ +/****************************************************************************** -SILC_SERVER_CMD_FUNC(nick) -{ - SilcServerCommandContext cmd = (SilcServerCommandContext)context; - SilcClientList *id_entry = (SilcClientList *)cmd->sock->user_data; - SilcServer server = cmd->server; - SilcBuffer packet, sp_buf; - SilcClientID *new_id; - char *id_string; - char *nick; + WHOWAS Functions - SILC_LOG_DEBUG(("Start")); +******************************************************************************/ -#define LCC(x) server->local_list->client_cache[(x) - 32] -#define LCCC(x) server->local_list->client_cache_count[(x) - 32] +static int +silc_server_command_whowas_parse(SilcServerCommandContext cmd, + char **nickname, + char **server_name, + int *count) +{ + unsigned char *tmp; + uint32 len; - /* Check number of arguments */ - if (silc_command_get_arg_num(cmd->payload) < 1) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK, + tmp = silc_argument_get_arg_type(cmd->args, 1, &len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_WHOWAS, SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); - goto out; - } - - /* Check nickname */ - nick = silc_command_get_arg_type(cmd->payload, 1, NULL); - if (silc_server_command_bad_chars(nick) == TRUE) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK, - SILC_STATUS_ERR_BAD_NICKNAME); - goto out; + return FALSE; } - /* Create new Client ID */ - silc_id_create_client_id(cmd->server->id, cmd->server->rng, - cmd->server->md5hash, nick, - &new_id); + /* Get the nickname@server string and parse it. */ + silc_parse_userfqdn(tmp, nickname, server_name); - /* Send notify about nickname change to our router. We send the new - ID and ask to replace it with the old one. */ - if (cmd->server->server_type == SILC_SERVER && !cmd->server->standalone) - silc_server_send_replace_id(server, server->id_entry->router->connection, - FALSE, id_entry->id, - SILC_ID_CLIENT, SILC_ID_CLIENT_LEN, - new_id, SILC_ID_CLIENT, SILC_ID_CLIENT_LEN); - - /* If we are router we have to distribute the new Client ID to all - routers in SILC. */ - if (cmd->server->server_type == SILC_ROUTER && !cmd->server->standalone) - silc_server_send_replace_id(server, server->id_entry->router->connection, - TRUE, id_entry->id, - SILC_ID_CLIENT, SILC_ID_CLIENT_LEN, - new_id, SILC_ID_CLIENT, SILC_ID_CLIENT_LEN); + /* Get the max count of reply messages allowed */ + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (tmp) + *count = atoi(tmp); + else + *count = 0; - /* Remove old cache entry */ - silc_idcache_del_by_id(LCC(id_entry->nickname[0]), - LCCC(id_entry->nickname[0]), - SILC_ID_CLIENT, id_entry->id); - - /* Free old ID */ - if (id_entry->id) { - memset(id_entry->id, 0, SILC_ID_CLIENT_LEN); - silc_free(id_entry->id); - } + return TRUE; +} - /* Save the nickname as this client is our local client */ - if (id_entry->nickname) - silc_free(id_entry->nickname); +static char +silc_server_command_whowas_check(SilcServerCommandContext cmd, + SilcClientEntry *clients, + uint32 clients_count) +{ + SilcServer server = cmd->server; + int i; + SilcClientEntry entry; - id_entry->nickname = strdup(nick); - id_entry->id = new_id; + for (i = 0; i < clients_count; i++) { + entry = clients[i]; - /* Update client cache */ - LCCC(nick[0]) = silc_idcache_add(&LCC(nick[0]), LCCC(nick[0]), - id_entry->nickname, SILC_ID_CLIENT, - id_entry->id, (void *)id_entry); + if (!entry->nickname || !entry->username) { + SilcBuffer tmpbuf; + uint16 old_ident; - /* Send the new Client ID as reply command back to client */ - id_string = silc_id_id2str(id_entry->id, SILC_ID_CLIENT); - sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0); - packet = silc_command_encode_payload_va(SILC_COMMAND_NICK, 2, - sp_buf->data, sp_buf->len, - id_string, SILC_ID_CLIENT_LEN); - silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY, - 0, packet->data, packet->len, FALSE); + if (!entry->router) + continue; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + /* Send WHOWAS command */ + silc_server_packet_send(server, entry->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply */ + silc_server_command_pending(server, SILC_COMMAND_WHOWAS, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_whowas, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_command_set_ident(cmd->payload, old_ident); - silc_free(id_string); + silc_buffer_free(tmpbuf); + return FALSE; + } + } + + return TRUE; +} + +static void +silc_server_command_whowas_send_reply(SilcServerCommandContext cmd, + SilcClientEntry *clients, + uint32 clients_count) +{ + SilcServer server = cmd->server; + char *tmp; + int i, count = 0, len; + SilcBuffer packet, idp; + SilcClientEntry entry = NULL; + SilcCommandStatus status; + uint16 ident = silc_command_get_ident(cmd->payload); + char found = FALSE; + char nh[256], uh[256]; + + status = SILC_STATUS_OK; + if (clients_count > 1) + status = SILC_STATUS_LIST_START; + + for (i = 0; i < clients_count; i++) { + entry = clients[i]; + + /* We will take only clients that are not valid anymore. They are the + ones that are not registered anymore but still have a ID. They + have disconnected us, and thus valid for WHOWAS. */ + if (entry->data.status & SILC_IDLIST_STATUS_REGISTERED) + continue; + if (entry->id == NULL) + continue; + + if (count && i - 1 == count) + break; + + found = TRUE; + + if (clients_count > 2) + status = SILC_STATUS_LIST_ITEM; + + if (clients_count > 1 && i == clients_count - 1) + status = SILC_STATUS_LIST_END; + + /* Sanity check, however these should never fail. However, as + this sanity check has been added here they have failed. */ + if (!entry->nickname || !entry->username) + continue; + + /* Send WHOWAS reply */ + idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + tmp = silc_argument_get_first_arg(cmd->args, NULL); + + memset(uh, 0, sizeof(uh)); + memset(nh, 0, sizeof(nh)); + + strncat(nh, entry->nickname, strlen(entry->nickname)); + if (!strchr(entry->nickname, '@')) { + strncat(nh, "@", 1); + if (entry->servername) { + strncat(nh, entry->servername, strlen(entry->servername)); + } else { + len = entry->router ? strlen(entry->router->server_name) : + strlen(server->server_name); + strncat(nh, entry->router ? entry->router->server_name : + server->server_name, len); + } + } + + strncat(uh, entry->username, strlen(entry->username)); + if (!strchr(entry->username, '@')) { + strncat(uh, "@", 1); + strcat(uh, "*private*"); + } + + if (entry->userinfo) + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_WHOWAS, + status, ident, 4, + 2, idp->data, idp->len, + 3, nh, strlen(nh), + 4, uh, strlen(uh), + 5, entry->userinfo, + strlen(entry->userinfo)); + else + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_WHOWAS, + status, ident, 3, + 2, idp->data, idp->len, + 3, nh, strlen(nh), + 4, uh, strlen(uh)); + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, + 0, packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(idp); + } + + if (found == FALSE && entry) + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOWAS, + SILC_STATUS_ERR_NO_SUCH_NICK, + 3, entry->nickname, + strlen(entry->nickname)); +} + +static int +silc_server_command_whowas_process(SilcServerCommandContext cmd) +{ + SilcServer server = cmd->server; + char *nick = NULL, *server_name = NULL; + int count = 0; + SilcClientEntry *clients = NULL; + uint32 clients_count = 0; + int ret = 0; + bool check_global = FALSE; + + /* Protocol dictates that we must always send the received WHOWAS request + to our router if we are normal server, so let's do it now unless we + are standalone. We will not send any replies to the client until we + have received reply from the router. */ + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT && + server->server_type == SILC_SERVER && !cmd->pending && + !server->standalone) { + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + /* Send WHOWAS command to our router */ + silc_server_packet_send(server, (SilcSocketConnection) + server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_WHOWAS, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_whowas, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_command_set_ident(cmd->payload, old_ident); + + silc_buffer_free(tmpbuf); + ret = -1; + goto out; + } + + /* We are ready to process the command request. Let's search for the + requested client and send reply to the requesting client. */ + + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) + check_global = TRUE; + else if (server->server_type != SILC_SERVER) + check_global = TRUE; + + /* Parse the whowas request */ + if (!silc_server_command_whowas_parse(cmd, &nick, &server_name, &count)) + return 0; + + /* Get all clients matching that nickname from local list */ + if (!silc_idlist_get_clients_by_nickname(server->local_list, + nick, server_name, + &clients, &clients_count)) + silc_idlist_get_clients_by_hash(server->local_list, + nick, server->md5hash, + &clients, &clients_count); + + /* Check global list as well */ + if (check_global) { + if (!silc_idlist_get_clients_by_nickname(server->global_list, + nick, server_name, + &clients, &clients_count)) + silc_idlist_get_clients_by_hash(server->global_list, + nick, server->md5hash, + &clients, &clients_count); + } + + if (!clients) { + /* Such a client really does not exist in the SILC network. */ + silc_server_command_send_status_data(cmd, SILC_COMMAND_WHOWAS, + SILC_STATUS_ERR_NO_SUCH_NICK, + 3, nick, strlen(nick)); + goto out; + } + + if (!silc_server_command_whowas_check(cmd, clients, clients_count)) { + ret = -1; + goto out; + } + + /* Send the command reply to the client */ + silc_server_command_whowas_send_reply(cmd, clients, clients_count); + + out: + silc_free(clients); + silc_free(nick); + silc_free(server_name); + + return ret; +} + +/* Server side of command WHOWAS. */ + +SILC_SERVER_CMD_FUNC(whowas) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + int ret = 0; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_WHOWAS, cmd, 1, 2); + + ret = silc_server_command_whowas_process(cmd); + + if (!ret) + silc_server_command_free(cmd); +} + +/****************************************************************************** + + IDENTIFY Functions + +******************************************************************************/ + +static bool +silc_server_command_identify_parse(SilcServerCommandContext cmd, + SilcClientEntry **clients, + uint32 *clients_count, + SilcServerEntry **servers, + uint32 *servers_count, + SilcChannelEntry **channels, + uint32 *channels_count, + uint32 *count) +{ + SilcServer server = cmd->server; + unsigned char *tmp; + uint32 len; + uint32 argc = silc_argument_get_arg_num(cmd->args); + SilcIDPayload idp; + bool check_global = FALSE; + void *entry; + int i; + bool error = FALSE; + + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) + check_global = TRUE; + else if (server->server_type != SILC_SERVER) + check_global = TRUE; + + /* If ID Payload is in the command it must be used instead of names */ + tmp = silc_argument_get_arg_type(cmd->args, 5, &len); + if (!tmp) { + /* No ID, get the names. */ + + /* Try to get nickname@server. */ + tmp = silc_argument_get_arg_type(cmd->args, 1, NULL); + if (tmp) { + char *nick = NULL; + char *nick_server = NULL; + + silc_parse_userfqdn(tmp, &nick, &nick_server); + + if (!silc_idlist_get_clients_by_hash(server->local_list, + nick, server->md5hash, + clients, clients_count)) + silc_idlist_get_clients_by_nickname(server->local_list, + nick, nick_server, + clients, clients_count); + if (check_global) { + if (!silc_idlist_get_clients_by_hash(server->global_list, + nick, server->md5hash, + clients, clients_count)) + silc_idlist_get_clients_by_nickname(server->global_list, + nick, nick_server, + clients, clients_count); + } + + silc_free(nick); + silc_free(nick_server); + + if (!(*clients)) { + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_NICK, + 3, tmp, strlen(tmp)); + return FALSE; + } + } + + /* Try to get server name */ + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (tmp) { + entry = silc_idlist_find_server_by_name(server->local_list, + tmp, TRUE, NULL); + if (!entry && check_global) + entry = silc_idlist_find_server_by_name(server->global_list, + tmp, TRUE, NULL); + if (entry) { + *servers = silc_realloc(*servers, sizeof(**servers) * + (*servers_count + 1)); + (*servers)[(*servers_count)++] = entry; + } + + if (!(*servers)) { + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_SERVER, + 3, tmp, strlen(tmp)); + return FALSE; + } + } + + /* Try to get channel name */ + tmp = silc_argument_get_arg_type(cmd->args, 3, NULL); + if (tmp) { + entry = silc_idlist_find_channel_by_name(server->local_list, + tmp, NULL); + if (!entry && check_global) + entry = silc_idlist_find_channel_by_name(server->global_list, + tmp, NULL); + if (entry) { + *channels = silc_realloc(*channels, sizeof(**channels) * + (*channels_count + 1)); + (*channels)[(*channels_count)++] = entry; + } + + if (!(*channels)) { + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_CHANNEL, + 3, tmp, strlen(tmp)); + return FALSE; + } + } + + if (!(*clients) && !(*servers) && !(*channels)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + return FALSE; + } + } else { + /* Command includes ID, we must use that. Also check whether the command + has more than one ID set - take them all. */ + + /* Take all ID's from the command packet */ + for (i = 0; i < argc; i++) { + void *id; + + tmp = silc_argument_get_arg_type(cmd->args, i + 5, &len); + if (!tmp) + continue; + + idp = silc_id_payload_parse_data(tmp, len); + if (!idp) { + silc_free(*clients); + silc_free(*servers); + silc_free(*channels); + silc_server_command_send_status_reply(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + return FALSE; + } + + id = silc_id_payload_get_id(idp); + + switch (silc_id_payload_get_type(idp)) { + + case SILC_ID_CLIENT: + entry = (void *)silc_idlist_find_client_by_id(server->local_list, + id, TRUE, NULL); + if (!entry && check_global) + entry = (void *)silc_idlist_find_client_by_id(server->global_list, + id, TRUE, NULL); + if (entry) { + *clients = silc_realloc(*clients, sizeof(**clients) * + (*clients_count + 1)); + (*clients)[(*clients_count)++] = (SilcClientEntry)entry; + } else { + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID, + 2, tmp, len); + error = TRUE; + } + + break; + + case SILC_ID_SERVER: + entry = (void *)silc_idlist_find_server_by_id(server->local_list, + id, TRUE, NULL); + if (!entry && check_global) + entry = (void *)silc_idlist_find_server_by_id(server->global_list, + id, TRUE, NULL); + if (entry) { + *servers = silc_realloc(*servers, sizeof(**servers) * + (*servers_count + 1)); + (*servers)[(*servers_count)++] = (SilcServerEntry)entry; + } else { + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_SERVER_ID, + 2, tmp, len); + error = TRUE; + } + break; + + case SILC_ID_CHANNEL: + entry = (void *)silc_idlist_find_channel_by_id(server->local_list, + id, NULL); + if (!entry && check_global) + entry = (void *)silc_idlist_find_channel_by_id(server->global_list, + id, NULL); + if (entry) { + *channels = silc_realloc(*channels, sizeof(**channels) * + (*channels_count + 1)); + (*channels)[(*channels_count)++] = (SilcChannelEntry)entry; + } else { + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID, + 2, tmp, len); + error = TRUE; + } + break; + } + + silc_free(id); + } + } + + if (error) { + silc_free(*clients); + silc_free(*servers); + silc_free(*channels); + return FALSE; + } + + /* Get the max count of reply messages allowed */ + tmp = silc_argument_get_arg_type(cmd->args, 4, NULL); + if (tmp) + *count = atoi(tmp); + else + *count = 0; + + return TRUE; +} + +/* Checks that all mandatory fields in client entry are present. If not + then send WHOIS request to the server who owns the client. We use + WHOIS because we want to get as much information as possible at once. */ + +static bool +silc_server_command_identify_check_client(SilcServerCommandContext cmd, + SilcClientEntry *clients, + uint32 clients_count) +{ + SilcServer server = cmd->server; + SilcClientEntry entry; + SilcServerResolveContext resolve = NULL, r = NULL; + uint32 resolve_count = 0; + int i, k; + bool no_res = TRUE; + + for (i = 0; i < clients_count; i++) { + entry = clients[i]; + + if (!entry || entry->nickname || + !(entry->data.status & SILC_IDLIST_STATUS_REGISTERED) || + !entry->router) + continue; + + /* We need to resolve this entry since it is not complete */ + + if (!cmd->pending && entry->data.status & SILC_IDLIST_STATUS_RESOLVING) { + /* The entry is being resolved (and we are not the resolver) so attach + to the command reply and we're done with this one. */ + silc_server_command_pending(server, SILC_COMMAND_NONE, + entry->resolve_cmd_ident, + silc_server_command_destructor, + silc_server_command_identify, + silc_server_command_dup(cmd)); + no_res = FALSE; + } else { + if (entry->data.status & SILC_IDLIST_STATUS_RESOLVING) { + /* We've resolved this and it still is not ready. We'll return + and are that this will be handled again after it is resolved. */ + for (i = 0; i < resolve_count; i++) { + for (k = 0; k < r->res_argc; k++) + silc_free(r->res_argv[k]); + silc_free(r->res_argv); + silc_free(r->res_argv_lens); + silc_free(r->res_argv_types); + } + silc_free(resolve); + return FALSE; + } else { + /* We'll resolve this client */ + SilcBuffer idp; + + r = NULL; + for (k = 0; k < resolve_count; k++) { + if (resolve[k].router == entry->router) { + r = &resolve[k]; + break; + } + } + + if (!r) { + resolve = silc_realloc(resolve, sizeof(*resolve) * + (resolve_count + 1)); + r = &resolve[resolve_count]; + memset(r, 0, sizeof(*r)); + r->router = entry->router; + r->ident = ++server->cmd_ident; + resolve_count++; + } + + r->res_argv = silc_realloc(r->res_argv, sizeof(*r->res_argv) * + (r->res_argc + 1)); + r->res_argv_lens = silc_realloc(r->res_argv_lens, + sizeof(*r->res_argv_lens) * + (r->res_argc + 1)); + r->res_argv_types = silc_realloc(r->res_argv_types, + sizeof(*r->res_argv_types) * + (r->res_argc + 1)); + idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + r->res_argv[r->res_argc] = silc_calloc(idp->len, + sizeof(**r->res_argv)); + memcpy(r->res_argv[r->res_argc], idp->data, idp->len); + r->res_argv_lens[r->res_argc] = idp->len; + r->res_argv_types[r->res_argc] = r->res_argc + 3; + r->res_argc++; + silc_buffer_free(idp); + + entry->resolve_cmd_ident = r->ident; + entry->data.status |= SILC_IDLIST_STATUS_RESOLVING; + entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVED; + } + } + } + + /* Do the resolving */ + for (i = 0; i < resolve_count; i++) { + SilcBuffer res_cmd; + + r = &resolve[i]; + + /* Send WHOIS request. We send WHOIS since we're doing the requesting + now anyway so make it a good one. */ + res_cmd = silc_command_payload_encode(SILC_COMMAND_WHOIS, + r->res_argc, r->res_argv, + r->res_argv_lens, + r->res_argv_types, + r->ident); + silc_server_packet_send(server, r->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + res_cmd->data, res_cmd->len, FALSE); + + /* Reprocess this packet after received reply */ + silc_server_command_pending(server, SILC_COMMAND_WHOIS, + r->ident, + silc_server_command_destructor, + silc_server_command_identify, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_buffer_free(res_cmd); + for (k = 0; k < r->res_argc; k++) + silc_free(r->res_argv[k]); + silc_free(r->res_argv); + silc_free(r->res_argv_lens); + silc_free(r->res_argv_types); + no_res = FALSE; + } + silc_free(resolve); + + return no_res; +} + +static void +silc_server_command_identify_send_reply(SilcServerCommandContext cmd, + SilcClientEntry *clients, + uint32 clients_count, + SilcServerEntry *servers, + uint32 servers_count, + SilcChannelEntry *channels, + uint32 channels_count, + int count) +{ + SilcServer server = cmd->server; + int i, k, len; + SilcBuffer packet, idp; + SilcCommandStatus status; + uint16 ident = silc_command_get_ident(cmd->payload); + char nh[256], uh[256]; + SilcSocketConnection hsock; + + status = SILC_STATUS_OK; + + if (clients) { + SilcClientEntry entry; + + len = 0; + for (i = 0; i < clients_count; i++) + if (clients[i]->data.status & SILC_IDLIST_STATUS_REGISTERED) + len++; + + if (len == 0 && clients_count) { + entry = clients[0]; + if (entry->nickname) { + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_NICK, + 3, entry->nickname, + strlen(entry->nickname)); + } else { + SilcBuffer idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID, + 2, idp->data, idp->len); + silc_buffer_free(idp); + } + + return; + } + + if (len > 1) + status = SILC_STATUS_LIST_START; + + for (i = 0, k = 0; i < clients_count; i++) { + entry = clients[i]; + + if (!(entry->data.status & SILC_IDLIST_STATUS_REGISTERED)) { + if (clients_count == 1) { + SilcBuffer idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + silc_server_command_send_status_data(cmd, SILC_COMMAND_IDENTIFY, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID, + 2, idp->data, idp->len); + silc_buffer_free(idp); + } + continue; + } + + if (k >= 1) + status = SILC_STATUS_LIST_ITEM; + if (clients_count > 1 && k == clients_count - 1 + && !servers_count && !channels_count) + status = SILC_STATUS_LIST_END; + if (count && k - 1 == count) + status = SILC_STATUS_LIST_END; + if (count && k - 1 > count) + break; + + /* Send IDENTIFY reply */ + idp = silc_id_payload_encode(entry->id, SILC_ID_CLIENT); + + memset(uh, 0, sizeof(uh)); + memset(nh, 0, sizeof(nh)); + + strncat(nh, entry->nickname, strlen(entry->nickname)); + if (!strchr(entry->nickname, '@')) { + strncat(nh, "@", 1); + if (entry->servername) { + strncat(nh, entry->servername, strlen(entry->servername)); + } else { + len = entry->router ? strlen(entry->router->server_name) : + strlen(server->server_name); + strncat(nh, entry->router ? entry->router->server_name : + server->server_name, len); + } + } + + if (!entry->username) { + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY, + status, ident, 2, + 2, idp->data, idp->len, + 3, nh, strlen(nh)); + } else { + strncat(uh, entry->username, strlen(entry->username)); + if (!strchr(entry->username, '@')) { + strncat(uh, "@", 1); + hsock = (SilcSocketConnection)entry->connection; + len = strlen(hsock->hostname); + strncat(uh, hsock->hostname, len); + } + + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY, + status, ident, 3, + 2, idp->data, idp->len, + 3, nh, strlen(nh), + 4, uh, strlen(uh)); + } + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, + 0, packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(idp); + + k++; + } + } + + status = (status == SILC_STATUS_LIST_ITEM ? + SILC_STATUS_LIST_ITEM : SILC_STATUS_OK); + + if (servers) { + SilcServerEntry entry; + + if (status == SILC_STATUS_OK && servers_count > 1) + status = SILC_STATUS_LIST_START; + + for (i = 0, k = 0; i < servers_count; i++) { + entry = servers[i]; + + if (k >= 1) + status = SILC_STATUS_LIST_ITEM; + if (servers_count > 1 && k == servers_count - 1 && !channels_count) + status = SILC_STATUS_LIST_END; + if (count && k - 1 == count) + status = SILC_STATUS_LIST_END; + if (count && k - 1 > count) + break; + + /* Send IDENTIFY reply */ + idp = silc_id_payload_encode(entry->id, SILC_ID_SERVER); + if (entry->server_name) { + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY, + status, ident, 2, + 2, idp->data, idp->len, + 3, entry->server_name, + strlen(entry->server_name)); + } else { + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY, + status, ident, 1, + 2, idp->data, idp->len); + } + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, + 0, packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(idp); + + k++; + } + } + + status = (status == SILC_STATUS_LIST_ITEM ? + SILC_STATUS_LIST_ITEM : SILC_STATUS_OK); + + if (channels) { + SilcChannelEntry entry; + + if (status == SILC_STATUS_OK && channels_count > 1) + status = SILC_STATUS_LIST_START; + + for (i = 0, k = 0; i < channels_count; i++) { + entry = channels[i]; + + if (k >= 1) + status = SILC_STATUS_LIST_ITEM; + if (channels_count > 1 && k == channels_count - 1) + status = SILC_STATUS_LIST_END; + if (count && k - 1 == count) + status = SILC_STATUS_LIST_END; + if (count && k - 1 > count) + break; + + /* Send IDENTIFY reply */ + idp = silc_id_payload_encode(entry->id, SILC_ID_CHANNEL); + if (entry->channel_name) { + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY, + status, ident, 2, + 2, idp->data, idp->len, + 3, entry->channel_name, + strlen(entry->channel_name)); + } else { + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_IDENTIFY, + status, ident, 1, + 2, idp->data, idp->len); + } + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, + 0, packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(idp); + + k++; + } + } +} + +static int +silc_server_command_identify_process(SilcServerCommandContext cmd) +{ + SilcServer server = cmd->server; + uint32 count = 0; + int ret = 0; + SilcClientEntry *clients = NULL; + SilcServerEntry *servers = NULL; + SilcChannelEntry *channels = NULL; + uint32 clients_count = 0, servers_count = 0, channels_count = 0; + + /* Protocol dictates that we must always send the received IDENTIFY request + to our router if we are normal server, so let's do it now unless we + are standalone. We will not send any replies to the client until we + have received reply from the router. */ + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT && + server->server_type == SILC_SERVER && !cmd->pending && + !server->standalone) { + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + /* Send IDENTIFY command to our router */ + silc_server_packet_send(server, (SilcSocketConnection) + server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_IDENTIFY, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_identify, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_command_set_ident(cmd->payload, old_ident); + + silc_buffer_free(tmpbuf); + ret = -1; + goto out; + } + + /* We are ready to process the command request. Let's search for the + requested client and send reply to the requesting client. */ + + /* Parse the IDENTIFY request */ + if (!silc_server_command_identify_parse(cmd, + &clients, &clients_count, + &servers, &servers_count, + &channels, &channels_count, + &count)) + return 0; + + /* Check that all mandatory fields are present and request those data + from the server who owns the client if necessary. */ + if (clients && !silc_server_command_identify_check_client(cmd, clients, + clients_count)) { + ret = -1; + goto out; + } + + /* Send the command reply to the client */ + silc_server_command_identify_send_reply(cmd, + clients, clients_count, + servers, servers_count, + channels, channels_count, + count); + + out: + silc_free(clients); + silc_free(servers); + silc_free(channels); + + return ret; +} + +SILC_SERVER_CMD_FUNC(identify) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + int ret = 0; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_IDENTIFY, cmd, 1, 3328); + + ret = silc_server_command_identify_process(cmd); + + if (!ret) + silc_server_command_free(cmd); +} + +/* Checks string for bad characters and returns TRUE if they are found. */ + +static int silc_server_command_bad_chars(char *nick) +{ + int i; + + for (i = 0; i < strlen(nick); i++) { + if (!isascii(nick[i])) + return TRUE; + if (nick[i] <= 32) return TRUE; + if (nick[i] == ' ') return TRUE; + if (nick[i] == '\\') return TRUE; + if (nick[i] == '\"') return TRUE; + if (nick[i] == '*') return TRUE; + if (nick[i] == '?') return TRUE; + if (nick[i] == ',') return TRUE; + if (nick[i] == '@') return TRUE; + } + + return FALSE; +} + +/* Server side of command NICK. Sets nickname for user. Setting + nickname causes generation of a new client ID for the client. The + new client ID is sent to the client after changing the nickname. */ + +SILC_SERVER_CMD_FUNC(nick) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcServer server = cmd->server; + SilcBuffer packet, nidp, oidp; + SilcClientID *new_id; + char *nick; + uint16 ident = silc_command_get_ident(cmd->payload); + int nickfail = 0; + + if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_NICK, cmd, 1, 1); + + /* Check nickname */ + nick = silc_argument_get_arg_type(cmd->args, 1, NULL); + if (silc_server_command_bad_chars(nick) == TRUE) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_NICK, + SILC_STATUS_ERR_BAD_NICKNAME); + goto out; + } + + if (strlen(nick) > 128) + nick[128] = '\0'; + + /* Create new Client ID */ + while (!silc_id_create_client_id(cmd->server, cmd->server->id, + cmd->server->rng, + cmd->server->md5hash, nick, + &new_id)) { + nickfail++; + snprintf(&nick[strlen(nick) - 1], 1, "%d", nickfail); + } + + /* Send notify about nickname change to our router. We send the new + ID and ask to replace it with the old one. If we are router the + packet is broadcasted. Send NICK_CHANGE notify. */ + if (!server->standalone) + silc_server_send_notify_nick_change(server, server->router->connection, + server->server_type == SILC_SERVER ? + FALSE : TRUE, client->id, + new_id); + + oidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + + /* Remove old cache entry */ + silc_idcache_del_by_context(server->local_list->clients, client); + + /* Free old ID */ + silc_free(client->id); + + /* Save the nickname as this client is our local client */ + silc_free(client->nickname); + + client->nickname = strdup(nick); + client->id = new_id; + + /* Update client cache */ + silc_idcache_add(server->local_list->clients, client->nickname, + client->id, (void *)client, FALSE); + + nidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + + /* Send NICK_CHANGE notify to the client's channels */ + silc_server_send_notify_on_channels(server, NULL, client, + SILC_NOTIFY_TYPE_NICK_CHANGE, 2, + oidp->data, oidp->len, + nidp->data, nidp->len); + + /* Send the new Client ID as reply command back to client */ + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_NICK, + SILC_STATUS_OK, ident, 1, + 2, nidp->data, nidp->len); + silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY, + 0, packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(nidp); + silc_buffer_free(oidp); + + out: + silc_server_command_free(cmd); +} + +/* Sends the LIST command reply */ + +static void +silc_server_command_list_send_reply(SilcServerCommandContext cmd, + SilcChannelEntry *lch, + uint32 lch_count, + SilcChannelEntry *gch, + uint32 gch_count) +{ + int i; + SilcBuffer packet, idp; + SilcChannelEntry entry; + SilcCommandStatus status; + uint16 ident = silc_command_get_ident(cmd->payload); + char *topic; + unsigned char usercount[4]; + uint32 users; + + for (i = 0; i < lch_count; i++) + if (lch[i]->mode & SILC_CHANNEL_MODE_SECRET) + lch[i] = NULL; + for (i = 0; i < gch_count; i++) + if (gch[i]->mode & SILC_CHANNEL_MODE_SECRET) + gch[i] = NULL; + + status = SILC_STATUS_OK; + if ((lch_count + gch_count) > 1) + status = SILC_STATUS_LIST_START; + + /* Local list */ + for (i = 0; i < lch_count; i++) { + entry = lch[i]; + + if (!entry) + continue; + + if (i >= 1) + status = SILC_STATUS_LIST_ITEM; + + if (i == lch_count - 1 && gch_count) + break; + if (lch_count > 1 && i == lch_count - 1) + status = SILC_STATUS_LIST_END; + + idp = silc_id_payload_encode(entry->id, SILC_ID_CHANNEL); + + if (entry->mode & SILC_CHANNEL_MODE_PRIVATE) { + topic = "*private*"; + memset(usercount, 0, sizeof(usercount)); + } else { + topic = entry->topic; + users = silc_hash_table_count(entry->user_list); + SILC_PUT32_MSB(users, usercount); + } + + /* Send the reply */ + if (topic) + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_LIST, + status, ident, 4, + 2, idp->data, idp->len, + 3, entry->channel_name, + strlen(entry->channel_name), + 4, topic, strlen(topic), + 5, usercount, 4); + else + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_LIST, + status, ident, 3, + 2, idp->data, idp->len, + 3, entry->channel_name, + strlen(entry->channel_name), + 5, usercount, 4); + silc_server_packet_send(cmd->server, cmd->sock, + SILC_PACKET_COMMAND_REPLY, 0, packet->data, + packet->len, FALSE); + silc_buffer_free(packet); + silc_buffer_free(idp); + } + + status = i ? SILC_STATUS_LIST_ITEM : SILC_STATUS_OK; + + /* Global list */ + for (i = 0; i < gch_count; i++) { + entry = gch[i]; + + if (!entry) + continue; + + if (i >= 1) + status = SILC_STATUS_LIST_ITEM; + + if (gch_count > 1 && i == lch_count - 1) + status = SILC_STATUS_LIST_END; + + idp = silc_id_payload_encode(entry->id, SILC_ID_CHANNEL); + + if (entry->mode & SILC_CHANNEL_MODE_PRIVATE) { + topic = "*private*"; + memset(usercount, 0, sizeof(usercount)); + } else { + topic = entry->topic; + users = silc_hash_table_count(entry->user_list); + SILC_PUT32_MSB(users, usercount); + } + + /* Send the reply */ + if (topic) + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_LIST, + status, ident, 4, + 2, idp->data, idp->len, + 3, entry->channel_name, + strlen(entry->channel_name), + 4, topic, strlen(topic), + 5, usercount, 4); + else + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_LIST, + status, ident, 3, + 2, idp->data, idp->len, + 3, entry->channel_name, + strlen(entry->channel_name), + 5, usercount, 4); + silc_server_packet_send(cmd->server, cmd->sock, + SILC_PACKET_COMMAND_REPLY, 0, packet->data, + packet->len, FALSE); + silc_buffer_free(packet); + silc_buffer_free(idp); + } +} + +/* Server side of LIST command. This lists the channel of the requested + server. Secret channels are not listed. */ + +SILC_SERVER_CMD_FUNC(list) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcChannelID *channel_id = NULL; + unsigned char *tmp; + uint32 tmp_len; + SilcChannelEntry *lchannels = NULL, *gchannels = NULL; + uint32 lch_count = 0, gch_count = 0; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_LIST, cmd, 0, 2); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (tmp) { + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_LIST, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + } + + /* Get the channels from local list */ + lchannels = silc_idlist_get_channels(server->local_list, channel_id, + &lch_count); + + /* Get the channels from global list if we are router */ + if (server->server_type != SILC_SERVER) + gchannels = silc_idlist_get_channels(server->global_list, channel_id, + &gch_count); + + /* Send the reply */ + silc_server_command_list_send_reply(cmd, lchannels, lch_count, + gchannels, gch_count); + + out: + silc_server_command_free(cmd); +} + +/* Server side of TOPIC command. Sets topic for channel and/or returns + current topic to client. */ + +SILC_SERVER_CMD_FUNC(topic) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcChannelID *channel_id; + SilcChannelEntry channel; + SilcChannelClientEntry chl; + SilcBuffer packet, idp; + unsigned char *tmp; + uint32 argc, tmp_len; + uint16 ident = silc_command_get_ident(cmd->payload); + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_TOPIC, cmd, 1, 2); + + argc = silc_argument_get_arg_num(cmd->args); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + + /* Check whether the channel exists */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } + } + + if (argc > 1) { + /* Get the topic */ + tmp = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + if (strlen(tmp) > 256) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* See whether the client is on channel and has rights to change topic */ + if (!silc_hash_table_find(channel->user_list, client, NULL, + (void *)&chl)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC, + SILC_STATUS_ERR_NOT_ON_CHANNEL); + goto out; + } + + if (chl->mode == SILC_CHANNEL_UMODE_NONE) { + if (channel->mode & SILC_CHANNEL_MODE_TOPIC) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_TOPIC, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + } + + /* Set the topic for channel */ + silc_free(channel->topic); + channel->topic = strdup(tmp); + + /* Send TOPIC_SET notify type to the network */ + if (!server->standalone) + silc_server_send_notify_topic_set(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, client->id, + channel->topic); + + idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + + /* Send notify about topic change to all clients on the channel */ + silc_server_send_notify_to_channel(server, NULL, channel, TRUE, + SILC_NOTIFY_TYPE_TOPIC_SET, 2, + idp->data, idp->len, + channel->topic, strlen(channel->topic)); + silc_buffer_free(idp); + } + + /* Send the topic to client as reply packet */ + idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL); + if (channel->topic) + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_TOPIC, + SILC_STATUS_OK, ident, 2, + 2, idp->data, idp->len, + 3, channel->topic, + strlen(channel->topic)); + else + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_TOPIC, + SILC_STATUS_OK, ident, 1, + 2, idp->data, idp->len); + silc_server_packet_send(cmd->server, cmd->sock, SILC_PACKET_COMMAND_REPLY, + 0, packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(idp); + silc_free(channel_id); + + out: + silc_server_command_free(cmd); +} + +/* Server side of INVITE command. Invites some client to join some channel. + This command is also used to manage the invite list of the channel. */ + +SILC_SERVER_CMD_FUNC(invite) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcSocketConnection sock = cmd->sock, dest_sock; + SilcChannelClientEntry chl; + SilcClientEntry sender, dest; + SilcClientID *dest_id = NULL; + SilcChannelEntry channel; + SilcChannelID *channel_id = NULL; + SilcIDListData idata; + SilcBuffer idp, idp2, packet; + unsigned char *tmp, *add, *del; + uint32 len; + uint16 ident = silc_command_get_ident(cmd->payload); + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_INVITE, cmd, 1, 4); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + channel_id = silc_id_payload_parse_id(tmp, len); + if (!channel_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + + /* Get the channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } + } + + /* Check whether the sender of this command is on the channel. */ + sender = (SilcClientEntry)sock->user_data; + if (!silc_server_client_on_channel(sender, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NOT_ON_CHANNEL); + goto out; + } + + /* Check whether the channel is invite-only channel. If yes then the + sender of this command must be at least channel operator. */ + if (channel->mode & SILC_CHANNEL_MODE_INVITE) { + silc_hash_table_find(channel->user_list, sender, NULL, (void *)&chl); + if (chl->mode == SILC_CHANNEL_UMODE_NONE) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + } + + /* Get destination client ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &len); + if (tmp) { + char invite[512]; + + dest_id = silc_id_payload_parse_id(tmp, len); + if (!dest_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NO_CLIENT_ID); + goto out; + } + + /* Get the client entry */ + dest = silc_server_get_client_resolve(server, dest_id); + if (!dest) { + if (server->server_type != SILC_SERVER) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID); + goto out; + } + + /* The client info is being resolved. Reprocess this packet after + receiving the reply to the query. */ + silc_server_command_pending(server, SILC_COMMAND_WHOIS, + server->cmd_ident, + silc_server_command_destructor, + silc_server_command_invite, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + silc_free(channel_id); + silc_free(dest_id); + return; + } + + /* Check whether the requested client is already on the channel. */ + if (silc_server_client_on_channel(dest, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_USER_ON_CHANNEL); + goto out; + } + + /* Get route to the client */ + dest_sock = silc_server_get_client_route(server, NULL, 0, dest_id, &idata); + if (!dest_sock) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INVITE, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID); + goto out; + } + + memset(invite, 0, sizeof(invite)); + strncat(invite, dest->nickname, strlen(dest->nickname)); + strncat(invite, "!", 1); + strncat(invite, dest->username, strlen(dest->username)); + if (!strchr(dest->username, '@')) { + strncat(invite, "@", 1); + strncat(invite, cmd->sock->hostname, strlen(cmd->sock->hostname)); + } + + len = strlen(invite); + if (!channel->invite_list) + channel->invite_list = silc_calloc(len + 2, + sizeof(*channel->invite_list)); + else + channel->invite_list = silc_realloc(channel->invite_list, + sizeof(*channel->invite_list) * + (len + + strlen(channel->invite_list) + 2)); + strncat(channel->invite_list, invite, len); + strncat(channel->invite_list, ",", 1); + + /* Send notify to the client that is invited to the channel */ + idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL); + idp2 = silc_id_payload_encode(sender->id, SILC_ID_CLIENT); + silc_server_send_notify_dest(server, dest_sock, FALSE, dest_id, + SILC_ID_CLIENT, + SILC_NOTIFY_TYPE_INVITE, 3, + idp->data, idp->len, + channel->channel_name, + strlen(channel->channel_name), + idp2->data, idp2->len); + silc_buffer_free(idp); + silc_buffer_free(idp2); + } + + /* Add the client to the invite list of the channel */ + add = silc_argument_get_arg_type(cmd->args, 3, &len); + if (add) { + if (!channel->invite_list) + channel->invite_list = silc_calloc(len + 2, + sizeof(*channel->invite_list)); + else + channel->invite_list = silc_realloc(channel->invite_list, + sizeof(*channel->invite_list) * + (len + + strlen(channel->invite_list) + 2)); + if (add[len - 1] == ',') + add[len - 1] = '\0'; + + strncat(channel->invite_list, add, len); + strncat(channel->invite_list, ",", 1); + } + + /* Get the invite to be removed and remove it from the list */ + del = silc_argument_get_arg_type(cmd->args, 4, &len); + if (del && channel->invite_list) { + char *start, *end, *n; + + if (!strncmp(channel->invite_list, del, + strlen(channel->invite_list) - 1)) { + silc_free(channel->invite_list); + channel->invite_list = NULL; + } else { + start = strstr(channel->invite_list, del); + if (start && strlen(start) >= len) { + end = start + len; + n = silc_calloc(strlen(channel->invite_list) - len, sizeof(*n)); + strncat(n, channel->invite_list, start - channel->invite_list); + strncat(n, end + 1, ((channel->invite_list + + strlen(channel->invite_list)) - end) - 1); + silc_free(channel->invite_list); + channel->invite_list = n; + } + } + } + + /* Send notify to the primary router */ + if (!server->standalone) + silc_server_send_notify_invite(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, + sender->id, add, del); + + /* Send command reply */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &len); + + if (add || del) + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_INVITE, + SILC_STATUS_OK, ident, 2, + 2, tmp, len, + 3, channel->invite_list, + channel->invite_list ? + strlen(channel->invite_list) : 0); + else + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_INVITE, + SILC_STATUS_OK, ident, 1, + 2, tmp, len); + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + + out: + silc_free(dest_id); + silc_free(channel_id); + silc_server_command_free(cmd); +} + +typedef struct { + SilcServer server; + SilcSocketConnection sock; + char *signoff; +} *QuitInternal; + +/* Quits connection to client. This gets called if client won't + close the connection even when it has issued QUIT command. */ + +SILC_TASK_CALLBACK(silc_server_command_quit_cb) +{ + QuitInternal q = (QuitInternal)context; + + /* Free all client specific data, such as client entry and entires + on channels this client may be on. */ + silc_server_free_client_data(q->server, q->sock, q->sock->user_data, + TRUE, q->signoff); + q->sock->user_data = NULL; + + /* Close the connection on our side */ + silc_server_close_connection(q->server, q->sock); + + silc_free(q->signoff); + silc_free(q); +} + +/* Quits SILC session. This is the normal way to disconnect client. */ + +SILC_SERVER_CMD_FUNC(quit) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcSocketConnection sock = cmd->sock; + QuitInternal q; + unsigned char *tmp = NULL; + uint32 len = 0; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_QUIT, cmd, 0, 1); + + if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + + /* Get destination ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &len); + if (len > 128) + tmp = NULL; + + q = silc_calloc(1, sizeof(*q)); + q->server = server; + q->sock = sock; + q->signoff = tmp ? strdup(tmp) : NULL; + + /* We quit the connection with little timeout */ + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_command_quit_cb, (void *)q, + 0, 200000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + + out: + silc_server_command_free(cmd); +} + +/* Server side of command KILL. This command is used by router operator + to remove an client from the SILC Network temporarily. */ + +SILC_SERVER_CMD_FUNC(kill) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcClientEntry remote_client; + SilcClientID *client_id; + unsigned char *tmp, *comment; + uint32 tmp_len, tmp_len2; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_KILL, cmd, 1, 2); + + if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + + /* KILL command works only on router */ + if (server->server_type != SILC_ROUTER) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL, + SILC_STATUS_ERR_NO_ROUTER_PRIV); + goto out; + } + + /* Check whether client has the permissions. */ + if (!(client->mode & SILC_UMODE_ROUTER_OPERATOR)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL, + SILC_STATUS_ERR_NO_ROUTER_PRIV); + goto out; + } + + /* Get the client ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + client_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!client_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID); + goto out; + } + + /* Get the client entry */ + remote_client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!remote_client) { + remote_client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!remote_client) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID); + goto out; + } + } + + /* Get comment */ + comment = silc_argument_get_arg_type(cmd->args, 2, &tmp_len2); + if (tmp_len2 > 128) + comment = NULL; + + /* Send reply to the sender */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KILL, + SILC_STATUS_OK); + + /* Send the KILL notify packets. First send it to the channel, then + to our primary router and then directly to the client who is being + killed right now. */ + + /* Send KILLED notify to the channels. It is not sent to the client + as it will be sent differently destined directly to the client and not + to the channel. */ + silc_server_send_notify_on_channels(server, remote_client, + remote_client, SILC_NOTIFY_TYPE_KILLED, + comment ? 2 : 1, + tmp, tmp_len, + comment, comment ? tmp_len2 : 0); + + /* Send KILLED notify to primary route */ + if (!server->standalone) + silc_server_send_notify_killed(server, server->router->connection, TRUE, + remote_client->id, comment); + + /* Send KILLED notify to the client directly */ + silc_server_send_notify_killed(server, remote_client->connection ? + remote_client->connection : + remote_client->router->connection, FALSE, + remote_client->id, comment); + + /* Remove the client from all channels. This generates new keys to the + channels as well. */ + silc_server_remove_from_channels(server, NULL, remote_client, FALSE, + NULL, TRUE); + + /* Remove the client entry, If it is locally connected then we will also + disconnect the client here */ + if (remote_client->connection) { + /* Remove locally conneted client */ + SilcSocketConnection sock = remote_client->connection; + silc_server_free_client_data(server, sock, remote_client, FALSE, NULL); + silc_server_close_connection(server, sock); + } else { + /* Remove remote client */ + if (!silc_idlist_del_client(server->global_list, remote_client)) + silc_idlist_del_client(server->local_list, remote_client); + } + + out: + silc_server_command_free(cmd); +} + +/* Server side of command INFO. This sends information about us to + the client. If client requested specific server we will send the + command to that server. */ + +SILC_SERVER_CMD_FUNC(info) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcBuffer packet, idp; + unsigned char *tmp; + uint32 tmp_len; + char *dest_server, *server_info = NULL, *server_name; + uint16 ident = silc_command_get_ident(cmd->payload); + SilcServerEntry entry = NULL; + SilcServerID *server_id = NULL; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_INFO, cmd, 0, 2); + + /* Get server name */ + dest_server = silc_argument_get_arg_type(cmd->args, 1, NULL); + + /* Get Server ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (tmp) { + server_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!server_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO, + SILC_STATUS_ERR_NO_SERVER_ID); + goto out; + } + } + + if (server_id) { + /* Check whether we have this server cached */ + entry = silc_idlist_find_server_by_id(server->local_list, + server_id, TRUE, NULL); + if (!entry) { + entry = silc_idlist_find_server_by_id(server->global_list, + server_id, TRUE, NULL); + if (!entry && server->server_type != SILC_SERVER) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO, + SILC_STATUS_ERR_NO_SUCH_SERVER); + goto out; + } + } + } + + /* Some buggy servers has sent request to router about themselves. */ + if (server->server_type != SILC_SERVER && cmd->sock->user_data == entry) + goto out; + + if ((!dest_server && !server_id && !entry) || (entry && + entry == server->id_entry) || + (dest_server && !cmd->pending && + !strncasecmp(dest_server, server->server_name, strlen(dest_server)))) { + /* Send our reply */ + char info_string[256]; + + memset(info_string, 0, sizeof(info_string)); + snprintf(info_string, sizeof(info_string), + "location: %s server: %s admin: %s <%s>", + server->config->admin_info->location, + server->config->admin_info->server_type, + server->config->admin_info->admin_name, + server->config->admin_info->admin_email); + + server_info = info_string; + entry = server->id_entry; + } else { + /* Check whether we have this server cached */ + if (!entry && dest_server) { + entry = silc_idlist_find_server_by_name(server->global_list, + dest_server, TRUE, NULL); + if (!entry) { + entry = silc_idlist_find_server_by_name(server->local_list, + dest_server, TRUE, NULL); + } + } + + if (!cmd->pending && + server->server_type != SILC_SERVER && entry && !entry->server_info) { + /* Send to the server */ + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + silc_server_packet_send(server, entry->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_INFO, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_info, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + silc_command_set_ident(cmd->payload, old_ident); + silc_buffer_free(tmpbuf); + return; + } + + if (!entry && !cmd->pending && !server->standalone) { + /* Send to the primary router */ + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + silc_server_packet_send(server, server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_INFO, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_info, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + silc_command_set_ident(cmd->payload, old_ident); + silc_buffer_free(tmpbuf); + return; + } + } + + silc_free(server_id); + + if (!entry) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO, + SILC_STATUS_ERR_NO_SUCH_SERVER); + goto out; + } + + idp = silc_id_payload_encode(entry->id, SILC_ID_SERVER); + if (!server_info) + server_info = entry->server_info; + server_name = entry->server_name; + + /* Send the reply */ + if (server_info) + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_INFO, + SILC_STATUS_OK, ident, 3, + 2, idp->data, idp->len, + 3, server_name, + strlen(server_name), + 4, server_info, + strlen(server_info)); + else + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_INFO, + SILC_STATUS_OK, ident, 2, + 2, idp->data, idp->len, + 3, server_name, + strlen(server_name)); + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_buffer_free(idp); + + out: + silc_server_command_free(cmd); +} + +/* Server side of command PING. This just replies to the ping. */ + +SILC_SERVER_CMD_FUNC(ping) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcServerID *id; + uint32 len; + unsigned char *tmp; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_INFO, cmd, 1, 2); + + /* Get Server ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING, + SILC_STATUS_ERR_NO_SERVER_ID); + goto out; + } + id = silc_id_str2id(tmp, len, SILC_ID_SERVER); + if (!id) + goto out; + + if (SILC_ID_SERVER_COMPARE(id, server->id)) { + /* Send our reply */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING, + SILC_STATUS_OK); + } else { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_PING, + SILC_STATUS_ERR_NO_SUCH_SERVER); + goto out; + } + + silc_free(id); + + out: + silc_server_command_free(cmd); +} + +/* Internal routine to join channel. The channel sent to this function + has been either created or resolved from ID lists. This joins the sent + client to the channel. */ + +static void silc_server_command_join_channel(SilcServer server, + SilcServerCommandContext cmd, + SilcChannelEntry channel, + SilcClientID *client_id, + bool created, + bool create_key, + uint32 umode) +{ + SilcSocketConnection sock = cmd->sock; + unsigned char *tmp; + uint32 tmp_len, user_count; + unsigned char *passphrase = NULL, mode[4], tmp2[4], tmp3[4]; + SilcClientEntry client; + SilcChannelClientEntry chl; + SilcBuffer reply, chidp, clidp, keyp = NULL, user_list, mode_list; + uint16 ident = silc_command_get_ident(cmd->payload); + char check[512], check2[512]; + + SILC_LOG_DEBUG(("Start")); + + if (!channel) + return; + + /* Get the client entry */ + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) { + client = (SilcClientEntry)sock->user_data; + } else { + client = silc_server_get_client_resolve(server, client_id); + if (!client) { + if (cmd->pending) + goto out; + + /* The client info is being resolved. Reprocess this packet after + receiving the reply to the query. */ + silc_server_command_pending(server, SILC_COMMAND_WHOIS, + server->cmd_ident, NULL, + silc_server_command_join, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + return; + } + + cmd->pending = FALSE; + } + + /* + * Check channel modes + */ + + memset(check, 0, sizeof(check)); + memset(check2, 0, sizeof(check2)); + strncat(check, client->nickname, strlen(client->nickname)); + strncat(check, "!", 1); + strncat(check, client->username, strlen(client->username)); + if (!strchr(client->username, '@')) { + strncat(check, "@", 1); + strncat(check, cmd->sock->hostname, strlen(cmd->sock->hostname)); + } + + strncat(check2, client->nickname, strlen(client->nickname)); + if (!strchr(client->nickname, '@')) { + strncat(check2, "@", 1); + strncat(check2, server->server_name, strlen(server->server_name)); + } + strncat(check2, "!", 1); + strncat(check2, client->username, strlen(client->username)); + if (!strchr(client->username, '@')) { + strncat(check2, "@", 1); + strncat(check2, cmd->sock->hostname, strlen(cmd->sock->hostname)); + } + + /* Check invite list if channel is invite-only channel */ + if (channel->mode & SILC_CHANNEL_MODE_INVITE) { + if (!channel->invite_list || + (!silc_string_match(channel->invite_list, check) && + !silc_string_match(channel->invite_list, check2))) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_NOT_INVITED); + goto out; + } + } + + /* Check ban list if it exists. If the client's nickname, server, + username and/or hostname is in the ban list the access to the + channel is denied. */ + if (channel->ban_list) { + if (!channel->ban_list || + silc_string_match(channel->ban_list, check) || + silc_string_match(channel->ban_list, check2)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_BANNED_FROM_CHANNEL); + goto out; + } + } + + /* Get passphrase */ + tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + if (tmp) { + passphrase = silc_calloc(tmp_len, sizeof(*passphrase)); + memcpy(passphrase, tmp, tmp_len); + } + + /* Check the channel passphrase if set. */ + if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) { + if (!passphrase || !channel->passphrase || + memcmp(channel->passphrase, passphrase, + strlen(channel->passphrase))) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_BAD_PASSWORD); + goto out; + } + } + + /* Check user count limit if set. */ + if (channel->mode & SILC_CHANNEL_MODE_ULIMIT) { + if (silc_hash_table_count(channel->user_list) + 1 > + channel->user_limit) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_CHANNEL_IS_FULL); + goto out; + } + } + + /* + * Client is allowed to join to the channel. Make it happen. + */ + + /* Check whether the client already is on the channel */ + if (silc_server_client_on_channel(client, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_USER_ON_CHANNEL); + goto out; + } + + /* Generate new channel key as protocol dictates */ + if (create_key) { + if (!silc_server_create_channel_key(server, channel, 0)) + goto out; + + /* Send the channel key. This is broadcasted to the channel but is not + sent to the client who is joining to the channel. */ + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) + silc_server_send_channel_key(server, NULL, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); + } + + /* Join the client to the channel by adding it to channel's user list. + Add also the channel to client entry's channels list for fast cross- + referencing. */ + chl = silc_calloc(1, sizeof(*chl)); + chl->mode = umode; + chl->client = client; + chl->channel = channel; + silc_hash_table_add(channel->user_list, client, chl); + silc_hash_table_add(client->channels, channel, chl); + + /* Get users on the channel */ + silc_server_get_users_on_channel(server, channel, &user_list, &mode_list, + &user_count); + + /* Encode Client ID Payload of the original client who wants to join */ + clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + + /* Encode command reply packet */ + chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL); + SILC_PUT32_MSB(channel->mode, mode); + SILC_PUT32_MSB(created, tmp2); + SILC_PUT32_MSB(user_count, tmp3); + + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + tmp = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + keyp = silc_channel_key_payload_encode(SILC_ID_CHANNEL_LEN, tmp, + strlen(channel->channel_key-> + cipher->name), + channel->channel_key->cipher->name, + channel->key_len / 8, channel->key); + silc_free(tmp); + } + + reply = + silc_command_reply_payload_encode_va(SILC_COMMAND_JOIN, + SILC_STATUS_OK, ident, 13, + 2, channel->channel_name, + strlen(channel->channel_name), + 3, chidp->data, chidp->len, + 4, clidp->data, clidp->len, + 5, mode, 4, + 6, tmp2, 4, + 7, keyp ? keyp->data : NULL, + keyp ? keyp->len : 0, + 8, channel->ban_list, + channel->ban_list ? + strlen(channel->ban_list) : 0, + 9, channel->invite_list, + channel->invite_list ? + strlen(channel->invite_list) : 0, + 10, channel->topic, + channel->topic ? + strlen(channel->topic) : 0, + 11, silc_hmac_get_name(channel->hmac), + strlen(silc_hmac_get_name(channel-> + hmac)), + 12, tmp3, 4, + 13, user_list->data, user_list->len, + 14, mode_list->data, + mode_list->len); + + /* Send command reply */ + silc_server_packet_send(server, sock, SILC_PACKET_COMMAND_REPLY, 0, + reply->data, reply->len, FALSE); + + /* Send JOIN notify to locally connected clients on the channel. If + we are normal server then router will send or have sent JOIN notify + already. However since we've added the client already to our channel + we'll ignore it (in packet_receive.c) so we must send it here. If + we are router then this will send it to local clients and local + servers. */ + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_JOIN, 2, + clidp->data, clidp->len, + chidp->data, chidp->len); + + if (!cmd->pending) { + /* Send JOIN notify packet to our primary router */ + if (!server->standalone) + silc_server_send_notify_join(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, client->id); + + if (keyp) + /* Distribute the channel key to all backup routers. */ + silc_server_backup_send(server, NULL, SILC_PACKET_CHANNEL_KEY, 0, + keyp->data, keyp->len, FALSE, TRUE); + } + + silc_buffer_free(reply); + silc_buffer_free(clidp); + silc_buffer_free(chidp); + silc_buffer_free(keyp); + silc_buffer_free(user_list); + silc_buffer_free(mode_list); + + out: + silc_free(passphrase); +} + +/* Server side of command JOIN. Joins client into requested channel. If + the channel does not exist it will be created. */ + +SILC_SERVER_CMD_FUNC(join) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + uint32 tmp_len; + char *tmp, *channel_name = NULL, *cipher, *hmac; + SilcChannelEntry channel; + uint32 umode = 0; + bool created = FALSE, create_key = TRUE; + SilcClientID *client_id; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_JOIN, cmd, 1, 4); + + /* Get channel name */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + channel_name = tmp; + + if (strlen(channel_name) > 256) + channel_name[255] = '\0'; + + if (silc_server_command_bad_chars(channel_name) == TRUE) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_BAD_CHANNEL); + goto out; + } + + /* Get Client ID of the client who is joining to the channel */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + client_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!client_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Get cipher and hmac name */ + cipher = silc_argument_get_arg_type(cmd->args, 4, NULL); + hmac = silc_argument_get_arg_type(cmd->args, 5, NULL); + + /* See if the channel exists */ + channel = silc_idlist_find_channel_by_name(server->local_list, + channel_name, NULL); + + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) { + /* If this is coming from client the Client ID in the command packet must + be same as the client's ID. */ + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) { + SilcClientEntry entry = (SilcClientEntry)cmd->sock->user_data; + if (!SILC_ID_CLIENT_COMPARE(entry->id, client_id)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + } + + if (!channel || channel->disabled) { + /* Channel not found */ + + /* If we are standalone server we don't have a router, we just create + the channel by ourselves. */ + if (server->standalone) { + channel = silc_server_create_new_channel(server, server->id, cipher, + hmac, channel_name, TRUE); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_UNKNOWN_ALGORITHM); + goto out; + } + + umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO); + created = TRUE; + create_key = FALSE; + + } else { + + /* The channel does not exist on our server. If we are normal server + we will send JOIN command to our router which will handle the + joining procedure (either creates the channel if it doesn't exist + or joins the client to it). */ + if (server->server_type != SILC_ROUTER) { + SilcBuffer tmpbuf; + uint16 old_ident; + + /* If this is pending command callback then we've resolved + it and it didn't work, return since we've notified the + client already in the command reply callback. */ + if (cmd->pending) + goto out; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + /* Send JOIN command to our router */ + silc_server_packet_send(server, (SilcSocketConnection) + server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_JOIN, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_join, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + return; + } + + /* We are router and the channel does not seem exist so we will check + our global list as well for the channel. */ + channel = silc_idlist_find_channel_by_name(server->global_list, + channel_name, NULL); + if (!channel) { + /* Channel really does not exist, create it */ + channel = silc_server_create_new_channel(server, server->id, cipher, + hmac, channel_name, TRUE); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_UNKNOWN_ALGORITHM); + goto out; + } + + umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO); + created = TRUE; + create_key = FALSE; + } + } + } + } else { + if (!channel) { + /* Channel not found */ + + /* If the command came from router and we are normal server then + something went wrong with the joining as the channel was not found. + We can't do anything else but ignore this. */ + if (cmd->sock->type == SILC_SOCKET_TYPE_ROUTER || + server->server_type != SILC_ROUTER) + goto out; + + /* We are router and the channel does not seem exist so we will check + our global list as well for the channel. */ + channel = silc_idlist_find_channel_by_name(server->global_list, + channel_name, NULL); + if (!channel) { + /* Channel really does not exist, create it */ + channel = silc_server_create_new_channel(server, server->id, cipher, + hmac, channel_name, TRUE); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, + SILC_STATUS_ERR_UNKNOWN_ALGORITHM); + goto out; + } + + umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO); + created = TRUE; + create_key = FALSE; + } + } + } + + /* Check whether the channel was created by our router */ + if (cmd->pending && context2) { + SilcServerCommandReplyContext reply = + (SilcServerCommandReplyContext)context2; + if (silc_command_get(reply->payload) == SILC_COMMAND_JOIN) { + tmp = silc_argument_get_arg_type(reply->args, 6, NULL); + SILC_GET32_MSB(created, tmp); + create_key = FALSE; /* Router returned the key already */ + } + } + + /* If the channel does not have global users and is also empty the client + will be the channel founder and operator. */ + if (!channel->global_users && !silc_hash_table_count(channel->user_list)) + umode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO); + + /* Join to the channel */ + silc_server_command_join_channel(server, cmd, channel, client_id, + created, create_key, umode); + + silc_free(client_id); + + out: + silc_server_command_free(cmd); +} + +/* Server side of command MOTD. Sends server's current "message of the + day" to the client. */ + +SILC_SERVER_CMD_FUNC(motd) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcBuffer packet, idp; + char *motd, *dest_server; + uint32 motd_len; + uint16 ident = silc_command_get_ident(cmd->payload); + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_MOTD, cmd, 1, 1); + + /* Get server name */ + dest_server = silc_argument_get_arg_type(cmd->args, 1, NULL); + if (!dest_server) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_MOTD, + SILC_STATUS_ERR_NO_SUCH_SERVER); + goto out; + } + + if (!strncasecmp(dest_server, server->server_name, strlen(dest_server))) { + /* Send our MOTD */ + + idp = silc_id_payload_encode(server->id_entry->id, SILC_ID_SERVER); + + if (server->config && server->config->motd && + server->config->motd->motd_file) { + /* Send motd */ + motd = silc_file_readfile(server->config->motd->motd_file, &motd_len); + if (!motd) + goto out; + + motd[motd_len] = 0; + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_MOTD, + SILC_STATUS_OK, ident, 2, + 2, idp, idp->len, + 3, motd, motd_len); + goto out; + } else { + /* No motd */ + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_MOTD, + SILC_STATUS_OK, ident, 1, + 2, idp, idp->len); + } + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + silc_buffer_free(idp); + } else { + SilcServerEntry entry; + + /* Check whether we have this server cached */ + entry = silc_idlist_find_server_by_name(server->global_list, + dest_server, TRUE, NULL); + if (!entry) { + entry = silc_idlist_find_server_by_name(server->local_list, + dest_server, TRUE, NULL); + } + + if (server->server_type != SILC_SERVER && !cmd->pending && + entry && !entry->motd) { + /* Send to the server */ + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + silc_server_packet_send(server, entry->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_MOTD, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_motd, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + silc_command_set_ident(cmd->payload, old_ident); + silc_buffer_free(tmpbuf); + return; + } + + if (!entry && !cmd->pending && !server->standalone) { + /* Send to the primary router */ + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + silc_server_packet_send(server, server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_MOTD, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_motd, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + silc_command_set_ident(cmd->payload, old_ident); + silc_buffer_free(tmpbuf); + return; + } + + if (!entry) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_INFO, + SILC_STATUS_ERR_NO_SUCH_SERVER); + goto out; + } + + idp = silc_id_payload_encode(server->id_entry->id, SILC_ID_SERVER); + + if (entry->motd) + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_MOTD, + SILC_STATUS_OK, ident, 2, + 2, idp, idp->len, + 3, entry->motd, + strlen(entry->motd)); + else + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_MOTD, + SILC_STATUS_OK, ident, 1, + 2, idp, idp->len); + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + silc_buffer_free(idp); + } + + out: + silc_server_command_free(cmd); +} + +/* Server side of command UMODE. Client can use this command to set/unset + user mode. Client actually cannot set itself to be as server/router + operator so this can be used only to unset the modes. */ + +SILC_SERVER_CMD_FUNC(umode) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcBuffer packet; + unsigned char *tmp_mask; + uint32 mask; + uint16 ident = silc_command_get_ident(cmd->payload); + + if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_UMODE, cmd, 2, 2); + + /* Get the client's mode mask */ + tmp_mask = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!tmp_mask) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_UMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + SILC_GET32_MSB(mask, tmp_mask); + + /* + * Change the mode + */ + + if (mask & SILC_UMODE_SERVER_OPERATOR) { + if (!(client->mode & SILC_UMODE_SERVER_OPERATOR)) { + /* Cannot operator mode */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_UMODE, + SILC_STATUS_ERR_PERM_DENIED); + goto out; + } + } else { + if (client->mode & SILC_UMODE_SERVER_OPERATOR) + /* Remove the server operator rights */ + client->mode &= ~SILC_UMODE_SERVER_OPERATOR; + } + + if (mask & SILC_UMODE_ROUTER_OPERATOR) { + if (!(client->mode & SILC_UMODE_ROUTER_OPERATOR)) { + /* Cannot operator mode */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_UMODE, + SILC_STATUS_ERR_PERM_DENIED); + goto out; + } + } else { + if (client->mode & SILC_UMODE_ROUTER_OPERATOR) + /* Remove the router operator rights */ + client->mode &= ~SILC_UMODE_ROUTER_OPERATOR; + } + + if (mask & SILC_UMODE_GONE) { + client->mode |= SILC_UMODE_GONE; + } else { + if (client->mode & SILC_UMODE_GONE) + /* Remove the gone status */ + client->mode &= ~SILC_UMODE_GONE; + } + + /* Send UMODE change to primary router */ + if (!server->standalone) + silc_server_send_notify_umode(server, server->router->connection, TRUE, + client->id, client->mode); + + /* Send command reply to sender */ + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_UMODE, + SILC_STATUS_OK, ident, 1, + 2, tmp_mask, 4); + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + + out: + silc_server_command_free(cmd); +} + +/* Checks that client has rights to add or remove channel modes. If any + of the checks fails FALSE is returned. */ + +int silc_server_check_cmode_rights(SilcChannelEntry channel, + SilcChannelClientEntry client, + uint32 mode) +{ + int is_op = client->mode & SILC_CHANNEL_UMODE_CHANOP; + int is_fo = client->mode & SILC_CHANNEL_UMODE_CHANFO; + + /* Check whether has rights to change anything */ + if (!is_op && !is_fo) + return FALSE; + + /* Check whether has rights to change everything */ + if (is_op && is_fo) + return TRUE; + + /* We know that client is channel operator, check that they are not + changing anything that requires channel founder rights. Rest of the + modes are available automatically for channel operator. */ + + if (mode & SILC_CHANNEL_MODE_PRIVKEY) { + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) + if (is_op && !is_fo) + return FALSE; + } else { + if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) { + if (is_op && !is_fo) + return FALSE; + } + } + + if (mode & SILC_CHANNEL_MODE_PASSPHRASE) { + if (!(channel->mode & SILC_CHANNEL_MODE_PASSPHRASE)) + if (is_op && !is_fo) + return FALSE; + } else { + if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) { + if (is_op && !is_fo) + return FALSE; + } + } + + if (mode & SILC_CHANNEL_MODE_CIPHER) { + if (!(channel->mode & SILC_CHANNEL_MODE_CIPHER)) + if (is_op && !is_fo) + return FALSE; + } else { + if (channel->mode & SILC_CHANNEL_MODE_CIPHER) { + if (is_op && !is_fo) + return FALSE; + } + } + + if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) { + if (!(channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)) + if (is_op && !is_fo) + return FALSE; + } else { + if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) { + if (is_op && !is_fo) + return FALSE; + } + } + + return TRUE; +} + +/* Server side command of CMODE. Changes channel mode */ + +SILC_SERVER_CMD_FUNC(cmode) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcIDListData idata = (SilcIDListData)client; + SilcChannelID *channel_id; + SilcChannelEntry channel; + SilcChannelClientEntry chl; + SilcBuffer packet, cidp; + unsigned char *tmp, *tmp_id, *tmp_mask; + char *cipher = NULL, *hmac = NULL; + uint32 mode_mask, tmp_len, tmp_len2; + uint16 ident = silc_command_get_ident(cmd->payload); + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CMODE, cmd, 2, 7); + + /* Get Channel ID */ + tmp_id = silc_argument_get_arg_type(cmd->args, 1, &tmp_len2); + if (!tmp_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + channel_id = silc_id_payload_parse_id(tmp_id, tmp_len2); + if (!channel_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + + /* Get the channel mode mask */ + tmp_mask = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!tmp_mask) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + SILC_GET32_MSB(mode_mask, tmp_mask); + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } + } + + /* Check whether this client is on the channel */ + if (!silc_server_client_on_channel(client, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ON_CHANNEL); + goto out; + } + + /* Get entry to the channel user list */ + silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl); + + /* Check that client has rights to change any requested channel modes */ + if (!silc_server_check_cmode_rights(channel, chl, mode_mask)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + + /* + * Check the modes. Modes that requires nothing special operation are + * not checked here. + */ + + if (mode_mask & SILC_CHANNEL_MODE_PRIVKEY) { + /* Channel uses private keys to protect traffic. Client(s) has set the + key locally they want to use, server does not know that key. */ + /* Nothing interesting to do here */ + } else { + if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) { + /* The mode is removed and we need to generate and distribute + new channel key. Clients are not using private channel keys + anymore after this. */ + + /* Re-generate channel key */ + if (!silc_server_create_channel_key(server, channel, 0)) + goto out; + + /* Send the channel key. This sends it to our local clients and if + we are normal server to our router as well. */ + silc_server_send_channel_key(server, NULL, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); + + cipher = channel->channel_key->cipher->name; + hmac = (char *)silc_hmac_get_name(channel->hmac); + } + } + + if (mode_mask & SILC_CHANNEL_MODE_ULIMIT) { + /* User limit is set on channel */ + uint32 user_limit; + + /* Get user limit */ + tmp = silc_argument_get_arg_type(cmd->args, 3, NULL); + if (!tmp) { + if (!(channel->mode & SILC_CHANNEL_MODE_ULIMIT)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + } else { + SILC_GET32_MSB(user_limit, tmp); + channel->user_limit = user_limit; + } + } else { + if (channel->mode & SILC_CHANNEL_MODE_ULIMIT) + /* User limit mode is unset. Remove user limit */ + channel->user_limit = 0; + } + + if (mode_mask & SILC_CHANNEL_MODE_PASSPHRASE) { + if (!(channel->mode & SILC_CHANNEL_MODE_PASSPHRASE)) { + /* Passphrase has been set to channel */ + + /* Get the passphrase */ + tmp = silc_argument_get_arg_type(cmd->args, 4, NULL); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Save the passphrase */ + channel->passphrase = strdup(tmp); + } + } else { + if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) { + /* Passphrase mode is unset. remove the passphrase */ + if (channel->passphrase) { + silc_free(channel->passphrase); + channel->passphrase = NULL; + } + } + } + + if (mode_mask & SILC_CHANNEL_MODE_CIPHER) { + if (!(channel->mode & SILC_CHANNEL_MODE_CIPHER)) { + /* Cipher to use protect the traffic */ + + /* Get cipher */ + cipher = silc_argument_get_arg_type(cmd->args, 5, NULL); + if (!cipher) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Delete old cipher and allocate the new one */ + silc_cipher_free(channel->channel_key); + if (!silc_cipher_alloc(cipher, &channel->channel_key)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_UNKNOWN_ALGORITHM); + goto out; + } + + /* Re-generate channel key */ + if (!silc_server_create_channel_key(server, channel, 0)) + goto out; + + /* Send the channel key. This sends it to our local clients and if + we are normal server to our router as well. */ + silc_server_send_channel_key(server, NULL, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); + } + } else { + if (channel->mode & SILC_CHANNEL_MODE_CIPHER) { + /* Cipher mode is unset. Remove the cipher and revert back to + default cipher */ + cipher = channel->cipher; + + /* Delete old cipher and allocate default one */ + silc_cipher_free(channel->channel_key); + if (!silc_cipher_alloc(cipher ? cipher : SILC_DEFAULT_CIPHER, + &channel->channel_key)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_UNKNOWN_ALGORITHM); + goto out; + } + + /* Re-generate channel key */ + if (!silc_server_create_channel_key(server, channel, 0)) + goto out; + + /* Send the channel key. This sends it to our local clients and if + we are normal server to our router as well. */ + silc_server_send_channel_key(server, NULL, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); + } + } + + if (mode_mask & SILC_CHANNEL_MODE_HMAC) { + if (!(channel->mode & SILC_CHANNEL_MODE_HMAC)) { + /* HMAC to use protect the traffic */ + unsigned char hash[32]; + + /* Get hmac */ + hmac = silc_argument_get_arg_type(cmd->args, 6, NULL); + if (!hmac) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Delete old hmac and allocate the new one */ + silc_hmac_free(channel->hmac); + if (!silc_hmac_alloc(hmac, NULL, &channel->hmac)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_UNKNOWN_ALGORITHM); + goto out; + } + + /* Set the HMAC key out of current channel key. The client must do + this locally. */ + silc_hash_make(silc_hmac_get_hash(channel->hmac), channel->key, + channel->key_len / 8, + hash); + silc_hmac_set_key(channel->hmac, hash, + silc_hash_len(silc_hmac_get_hash(channel->hmac))); + memset(hash, 0, sizeof(hash)); + } + } else { + if (channel->mode & SILC_CHANNEL_MODE_HMAC) { + /* Hmac mode is unset. Remove the hmac and revert back to + default hmac */ + unsigned char hash[32]; + hmac = channel->hmac_name; + + /* Delete old hmac and allocate default one */ + silc_hmac_free(channel->hmac); + if (!silc_hmac_alloc(hmac ? hmac : SILC_DEFAULT_HMAC, NULL, + &channel->hmac)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_UNKNOWN_ALGORITHM); + goto out; + } + + /* Set the HMAC key out of current channel key. The client must do + this locally. */ + silc_hash_make(silc_hmac_get_hash(channel->hmac), channel->key, + channel->key_len / 8, + hash); + silc_hmac_set_key(channel->hmac, hash, + silc_hash_len(silc_hmac_get_hash(channel->hmac))); + memset(hash, 0, sizeof(hash)); + } + } + + if (mode_mask & SILC_CHANNEL_MODE_FOUNDER_AUTH) { + if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) { + if (!(channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)) { + /* Set the founder authentication */ + SilcAuthPayload auth; + + tmp = silc_argument_get_arg_type(cmd->args, 7, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + auth = silc_auth_payload_parse(tmp, tmp_len); + if (!auth) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Save the public key */ + tmp = silc_pkcs_public_key_encode(idata->public_key, &tmp_len); + silc_pkcs_public_key_decode(tmp, tmp_len, &channel->founder_key); + silc_free(tmp); + + channel->founder_method = silc_auth_get_method(auth); + + if (channel->founder_method == SILC_AUTH_PASSWORD) { + tmp = silc_auth_get_data(auth, &tmp_len); + channel->founder_passwd = + silc_calloc(tmp_len + 1, sizeof(*channel->founder_passwd)); + memcpy(channel->founder_passwd, tmp, tmp_len); + channel->founder_passwd_len = tmp_len; + } else { + /* Verify the payload before setting the mode */ + if (!silc_auth_verify(auth, channel->founder_method, + channel->founder_key, 0, idata->hash, + client->id, SILC_ID_CLIENT)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CMODE, + SILC_STATUS_ERR_AUTH_FAILED); + goto out; + } + } + + silc_auth_payload_free(auth); + } + } + } else { + if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) { + if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) { + if (channel->founder_key) + silc_pkcs_public_key_free(channel->founder_key); + if (channel->founder_passwd) { + silc_free(channel->founder_passwd); + channel->founder_passwd = NULL; + } + } + } + } + + /* Finally, set the mode */ + channel->mode = mode_mask; + + /* Send CMODE_CHANGE notify */ + cidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_CMODE_CHANGE, 4, + cidp->data, cidp->len, + tmp_mask, 4, + cipher, cipher ? strlen(cipher) : 0, + hmac, hmac ? strlen(hmac) : 0); + + /* Set CMODE notify type to network */ + if (!server->standalone) + silc_server_send_notify_cmode(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, + mode_mask, client->id, SILC_ID_CLIENT, + cipher, hmac); + + /* Send command reply to sender */ + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_CMODE, + SILC_STATUS_OK, ident, 2, + 2, tmp_id, tmp_len2, + 3, tmp_mask, 4); + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + silc_free(channel_id); + silc_free(cidp); + + out: + silc_server_command_free(cmd); +} + +/* Server side of CUMODE command. Changes client's mode on a channel. */ + +SILC_SERVER_CMD_FUNC(cumode) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcIDListData idata = (SilcIDListData)client; + SilcChannelID *channel_id; + SilcClientID *client_id; + SilcChannelEntry channel; + SilcClientEntry target_client; + SilcChannelClientEntry chl; + SilcBuffer packet, idp; + unsigned char *tmp_id, *tmp_ch_id, *tmp_mask; + uint32 target_mask, sender_mask = 0, tmp_len, tmp_ch_len; + int notify = FALSE; + uint16 ident = silc_command_get_ident(cmd->payload); + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CUMODE, cmd, 3, 4); + + /* Get Channel ID */ + tmp_ch_id = silc_argument_get_arg_type(cmd->args, 1, &tmp_ch_len); + if (!tmp_ch_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + channel_id = silc_id_payload_parse_id(tmp_ch_id, tmp_ch_len); + if (!channel_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } + } + + /* Check whether sender is on the channel */ + if (!silc_server_client_on_channel(client, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NOT_ON_CHANNEL); + goto out; + } + + /* Check that client has rights to change other's rights */ + silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl); + sender_mask = chl->mode; + + /* Get the target client's channel mode mask */ + tmp_mask = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!tmp_mask) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + SILC_GET32_MSB(target_mask, tmp_mask); + + /* Get target Client ID */ + tmp_id = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + if (!tmp_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_CLIENT_ID); + goto out; + } + client_id = silc_id_payload_parse_id(tmp_id, tmp_len); + if (!client_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_CLIENT_ID); + goto out; + } + + /* Get target client's entry */ + target_client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!target_client) { + target_client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + } + + if (target_client != client && + !(sender_mask & SILC_CHANNEL_UMODE_CHANFO) && + !(sender_mask & SILC_CHANNEL_UMODE_CHANOP)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + + /* Check whether target client is on the channel */ + if (target_client != client) { + if (!silc_server_client_on_channel(target_client, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_USER_NOT_ON_CHANNEL); + goto out; + } + + /* Get entry to the channel user list */ + silc_hash_table_find(channel->user_list, target_client, NULL, + (void *)&chl); + } + + /* + * Change the mode + */ + + /* If the target client is founder, no one else can change their mode + but themselves. */ + if (chl->mode & SILC_CHANNEL_UMODE_CHANFO && chl->client != target_client) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NOT_YOU); + goto out; + } + + if (target_mask & SILC_CHANNEL_UMODE_CHANFO) { + if (!(chl->mode & SILC_CHANNEL_UMODE_CHANFO)) { + /* The client tries to claim the founder rights. */ + unsigned char *tmp_auth; + uint32 tmp_auth_len, auth_len; + void *auth; + + if (target_client != client) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NOT_YOU); + goto out; + } + + if (!(channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) || + !channel->founder_key || !idata->public_key || + !silc_pkcs_public_key_compare(channel->founder_key, + idata->public_key)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NOT_YOU); + goto out; + } + + tmp_auth = silc_argument_get_arg_type(cmd->args, 4, &tmp_auth_len); + if (!tmp_auth) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + auth = (channel->founder_method == SILC_AUTH_PASSWORD ? + (void *)channel->founder_passwd : (void *)channel->founder_key); + auth_len = (channel->founder_method == SILC_AUTH_PASSWORD ? + channel->founder_passwd_len : 0); + + if (!silc_auth_verify_data(tmp_auth, tmp_auth_len, + channel->founder_method, auth, auth_len, + idata->hash, client->id, SILC_ID_CLIENT)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_AUTH_FAILED); + goto out; + } + + sender_mask = chl->mode |= SILC_CHANNEL_UMODE_CHANFO; + notify = TRUE; + } + } else { + if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) { + if (target_client == client) { + /* Remove channel founder rights from itself */ + chl->mode &= ~SILC_CHANNEL_UMODE_CHANFO; + notify = TRUE; + } else { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NOT_YOU); + goto out; + } + } + } + + if (target_mask & SILC_CHANNEL_UMODE_CHANOP) { + /* Promote to operator */ + if (!(chl->mode & SILC_CHANNEL_UMODE_CHANOP)) { + if (!(sender_mask & SILC_CHANNEL_UMODE_CHANOP) && + !(sender_mask & SILC_CHANNEL_UMODE_CHANFO)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + + chl->mode |= SILC_CHANNEL_UMODE_CHANOP; + notify = TRUE; + } + } else { + if (chl->mode & SILC_CHANNEL_UMODE_CHANOP) { + if (!(sender_mask & SILC_CHANNEL_UMODE_CHANOP) && + !(sender_mask & SILC_CHANNEL_UMODE_CHANFO)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CUMODE, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + + /* Demote to normal user */ + chl->mode &= ~SILC_CHANNEL_UMODE_CHANOP; + notify = TRUE; + } + } + + idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + tmp_id = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + + /* Send notify to channel, notify only if mode was actually changed. */ + if (notify) { + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_CUMODE_CHANGE, 3, + idp->data, idp->len, + tmp_mask, 4, + tmp_id, tmp_len); + + /* Set CUMODE notify type to network */ + if (!server->standalone) + silc_server_send_notify_cumode(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, + target_mask, client->id, + SILC_ID_CLIENT, + target_client->id); + } + + /* Send command reply to sender */ + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_CUMODE, + SILC_STATUS_OK, ident, 3, + 2, tmp_mask, 4, + 3, tmp_ch_id, tmp_ch_len, + 4, tmp_id, tmp_len); + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); - silc_free(sp_buf); + silc_free(channel_id); + silc_free(client_id); + silc_buffer_free(idp); + + out: + silc_server_command_free(cmd); +} + +/* Server side of KICK command. Kicks client out of channel. */ + +SILC_SERVER_CMD_FUNC(kick) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcClientEntry target_client; + SilcChannelID *channel_id; + SilcClientID *client_id; + SilcChannelEntry channel; + SilcChannelClientEntry chl; + SilcBuffer idp; + uint32 tmp_len; + unsigned char *tmp, *comment; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_LEAVE, cmd, 1, 3); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } + } + + /* Check whether sender is on the channel */ + if (!silc_server_client_on_channel(client, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NOT_ON_CHANNEL); + goto out; + } + + /* Check that the kicker is channel operator or channel founder */ + silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl); + if (chl->mode == SILC_CHANNEL_UMODE_NONE) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + + /* Get target Client ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NO_CLIENT_ID); + goto out; + } + client_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!client_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NO_CLIENT_ID); + goto out; + } + + /* Get target client's entry */ + target_client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!target_client) { + target_client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + } + + /* Check that the target client is not channel founder. Channel founder + cannot be kicked from the channel. */ + silc_hash_table_find(channel->user_list, target_client, NULL, (void *)&chl); + if (chl->mode & SILC_CHANNEL_UMODE_CHANFO) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_NO_CHANNEL_FOPRIV); + goto out; + } + + /* Check whether target client is on the channel */ + if (!silc_server_client_on_channel(target_client, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_ERR_USER_NOT_ON_CHANNEL); + goto out; + } + + /* Get comment */ + tmp_len = 0; + comment = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + if (tmp_len > 128) + comment = NULL; + + /* Send command reply to sender */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_KICK, + SILC_STATUS_OK); + + /* Send KICKED notify to local clients on the channel */ + idp = silc_id_payload_encode(target_client->id, SILC_ID_CLIENT); + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_KICKED, + comment ? 2 : 1, + idp->data, idp->len, + comment, comment ? strlen(comment) : 0); + silc_buffer_free(idp); + + /* Remove the client from the channel. If the channel does not exist + after removing the client then the client kicked itself off the channel + and we don't have to send anything after that. */ + if (!silc_server_remove_from_one_channel(server, NULL, channel, + target_client, FALSE)) + goto out; + + /* Send KICKED notify to primary route */ + if (!server->standalone) + silc_server_send_notify_kicked(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, + target_client->id, comment); + + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + /* Re-generate channel key */ + if (!silc_server_create_channel_key(server, channel, 0)) + goto out; + + /* Send the channel key to the channel. The key of course is not sent + to the client who was kicked off the channel. */ + silc_server_send_channel_key(server, target_client->connection, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); + } + + out: + silc_server_command_free(cmd); +} + +/* Server side of OPER command. Client uses this comand to obtain server + operator privileges to this server/router. */ + +SILC_SERVER_CMD_FUNC(oper) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + unsigned char *username, *auth; + uint32 tmp_len; + SilcServerConfigSectionAdminConnection *admin; + SilcIDListData idata = (SilcIDListData)client; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_OPER, cmd, 1, 2); + + if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + + /* Get the username */ + username = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!username) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Get the admin configuration */ + admin = silc_server_config_find_admin(server->config, cmd->sock->ip, + username, client->nickname); + if (!admin) { + admin = silc_server_config_find_admin(server->config, cmd->sock->hostname, + username, client->nickname); + if (!admin) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER, + SILC_STATUS_ERR_AUTH_FAILED); + goto out; + } + } + + /* Get the authentication payload */ + auth = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!auth) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Verify the authentication data */ + if (!silc_auth_verify_data(auth, tmp_len, admin->auth_meth, + admin->auth_data, admin->auth_data_len, + idata->hash, client->id, SILC_ID_CLIENT)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER, + SILC_STATUS_ERR_AUTH_FAILED); + goto out; + } + + /* Client is now server operator */ + client->mode |= SILC_UMODE_SERVER_OPERATOR; + + /* Send UMODE change to primary router */ + if (!server->standalone) + silc_server_send_notify_umode(server, server->router->connection, TRUE, + client->id, client->mode); + + /* Send reply to the sender */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_OPER, + SILC_STATUS_OK); + + out: + silc_server_command_free(cmd); +} + +/* Server side of SILCOPER command. Client uses this comand to obtain router + operator privileges to this router. */ + +SILC_SERVER_CMD_FUNC(silcoper) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + unsigned char *username, *auth; + uint32 tmp_len; + SilcServerConfigSectionAdminConnection *admin; + SilcIDListData idata = (SilcIDListData)client; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_SILCOPER, cmd, 1, 2); + + if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + + if (server->server_type != SILC_ROUTER) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER, + SILC_STATUS_ERR_AUTH_FAILED); + goto out; + } + + /* Get the username */ + username = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!username) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Get the admin configuration */ + admin = silc_server_config_find_admin(server->config, cmd->sock->ip, + username, client->nickname); + if (!admin) { + admin = silc_server_config_find_admin(server->config, cmd->sock->hostname, + username, client->nickname); + if (!admin) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER, + SILC_STATUS_ERR_AUTH_FAILED); + goto out; + } + } + + /* Get the authentication payload */ + auth = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!auth) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Verify the authentication data */ + if (!silc_auth_verify_data(auth, tmp_len, admin->auth_meth, + admin->auth_data, admin->auth_data_len, + idata->hash, client->id, SILC_ID_CLIENT)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER, + SILC_STATUS_ERR_AUTH_FAILED); + goto out; + } + + /* Client is now router operator */ + client->mode |= SILC_UMODE_ROUTER_OPERATOR; + + /* Send UMODE change to primary router */ + if (!server->standalone) + silc_server_send_notify_umode(server, server->router->connection, TRUE, + client->id, client->mode); + + /* Send reply to the sender */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SILCOPER, + SILC_STATUS_OK); + + out: + silc_server_command_free(cmd); +} + +/* Server side command of CONNECT. Connects us to the specified remote + server or router. */ + +SILC_SERVER_CMD_FUNC(connect) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + unsigned char *tmp, *host; + uint32 tmp_len; + uint32 port = SILC_PORT; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CONNECT, cmd, 1, 2); + + if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; + + /* Check whether client has the permissions. */ + if (client->mode == SILC_UMODE_NONE) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT, + SILC_STATUS_ERR_NO_SERVER_PRIV); + goto out; + } + + if (server->server_type == SILC_ROUTER && + client->mode & SILC_UMODE_SERVER_OPERATOR) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT, + SILC_STATUS_ERR_NO_ROUTER_PRIV); + goto out; + } + + /* Get the remote server */ + host = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!host) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + + /* Get port */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (tmp) + SILC_GET32_MSB(port, tmp); + + /* Create the connection. It is done with timeout and is async. */ + silc_server_create_connection(server, host, port); + + /* Send reply to the sender */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CONNECT, + SILC_STATUS_OK); out: silc_server_command_free(cmd); -#undef LCC -#undef LCCC } -SILC_SERVER_CMD_FUNC(list) -{ -} +/* Server side of command BAN. This is used to manage the ban list of the + channel. To add clients and remove clients from the ban list. */ -SILC_SERVER_CMD_FUNC(topic) +SILC_SERVER_CMD_FUNC(ban) { -} + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcBuffer packet; + SilcChannelEntry channel; + SilcChannelClientEntry chl; + SilcChannelID *channel_id = NULL; + unsigned char *id, *add, *del; + uint32 id_len, tmp_len; + uint16 ident = silc_command_get_ident(cmd->payload); + + if (cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; -SILC_SERVER_CMD_FUNC(invite) -{ -} + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_BAN, cmd, 0, 3); -/* Quits connection to client. This gets called if client won't - close the connection even when it has issued QUIT command. */ + /* Get Channel ID */ + id = silc_argument_get_arg_type(cmd->args, 1, &id_len); + if (id) { + channel_id = silc_id_payload_parse_id(id, id_len); + if (!channel_id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + } -SILC_TASK_CALLBACK(silc_server_command_quit_cb) -{ - SilcServer server = (SilcServer)context; - SilcSocketConnection sock = server->sockets[fd]; + /* Get channel entry. The server must know about the channel since the + client is expected to be on the channel. */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } + } - /* Free all client specific data, such as client entry and entires - on channels this client may be on. */ - silc_server_free_sock_user_data(server, sock); + /* Check whether this client is on the channel */ + if (!silc_server_client_on_channel(client, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN, + SILC_STATUS_ERR_NOT_ON_CHANNEL); + goto out; + } - /* Close the connection on our side */ - silc_server_close_connection(server, sock); + /* Get entry to the channel user list */ + silc_hash_table_find(channel->user_list, client, NULL, (void *)&chl); + + /* The client must be at least channel operator. */ + if (!(chl->mode & SILC_CHANNEL_UMODE_CHANOP)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_BAN, + SILC_STATUS_ERR_NO_CHANNEL_PRIV); + goto out; + } + + /* Get the new ban and add it to the ban list */ + add = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (add) { + if (!channel->ban_list) + channel->ban_list = silc_calloc(tmp_len + 2, sizeof(*channel->ban_list)); + else + channel->ban_list = silc_realloc(channel->ban_list, + sizeof(*channel->ban_list) * + (tmp_len + + strlen(channel->ban_list) + 2)); + if (add[tmp_len - 1] == ',') + add[tmp_len - 1] = '\0'; + + strncat(channel->ban_list, add, tmp_len); + strncat(channel->ban_list, ",", 1); + } + + /* Get the ban to be removed and remove it from the list */ + del = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + if (del && channel->ban_list) { + char *start, *end, *n; + + if (!strncmp(channel->ban_list, del, strlen(channel->ban_list) - 1)) { + silc_free(channel->ban_list); + channel->ban_list = NULL; + } else { + start = strstr(channel->ban_list, del); + if (start && strlen(start) >= tmp_len) { + end = start + tmp_len; + n = silc_calloc(strlen(channel->ban_list) - tmp_len, sizeof(*n)); + strncat(n, channel->ban_list, start - channel->ban_list); + strncat(n, end + 1, ((channel->ban_list + strlen(channel->ban_list)) - + end) - 1); + silc_free(channel->ban_list); + channel->ban_list = n; + } + } + } + + /* Send the BAN notify type to our primary router. */ + if (!server->standalone && (add || del)) + silc_server_send_notify_ban(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, add, del); + + /* Send the reply back to the client */ + if (channel->ban_list) + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_BAN, + SILC_STATUS_OK, ident, 2, + 2, id, id_len, + 3, channel->ban_list, + strlen(channel->ban_list) - 1); + else + packet = + silc_command_reply_payload_encode_va(SILC_COMMAND_BAN, + SILC_STATUS_OK, ident, 1, + 2, id, id_len); + + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + + silc_buffer_free(packet); + + out: + silc_free(channel_id); + silc_server_command_free(cmd); } -/* Quits SILC session. This is the normal way to disconnect client. */ +/* Server side command of CLOSE. Closes connection to a specified server. */ -SILC_SERVER_CMD_FUNC(quit) +SILC_SERVER_CMD_FUNC(close) { SilcServerCommandContext cmd = (SilcServerCommandContext)context; SilcServer server = cmd->server; - SilcSocketConnection sock = cmd->sock; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; + SilcServerEntry server_entry; + SilcSocketConnection sock; + unsigned char *tmp; + uint32 tmp_len; + unsigned char *name; + uint32 port = SILC_PORT; - SILC_LOG_DEBUG(("Start")); + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_CLOSE, cmd, 1, 2); - /* We quit the connection with little timeout */ - silc_task_register(server->timeout_queue, sock->sock, - silc_server_command_quit_cb, server, - 0, 300000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; - silc_server_command_free(cmd); -} + /* Check whether client has the permissions. */ + if (client->mode == SILC_UMODE_NONE) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE, + SILC_STATUS_ERR_NO_SERVER_PRIV); + goto out; + } -SILC_SERVER_CMD_FUNC(kill) -{ -} + /* Get the remote server */ + name = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!name) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } -SILC_SERVER_CMD_FUNC(info) -{ -} + /* Get port */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (tmp) + SILC_GET32_MSB(port, tmp); + + server_entry = silc_idlist_find_server_by_conn(server->local_list, + name, port, FALSE, NULL); + if (!server_entry) + server_entry = silc_idlist_find_server_by_conn(server->global_list, + name, port, FALSE, NULL); + if (!server_entry) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE, + SILC_STATUS_ERR_NO_SERVER_ID); + goto out; + } -SILC_SERVER_CMD_FUNC(connect) -{ -} + /* Send reply to the sender */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_CLOSE, + SILC_STATUS_OK); -SILC_SERVER_CMD_FUNC(ping) -{ + /* Close the connection to the server */ + sock = (SilcSocketConnection)server_entry->connection; + + /* If we shutdown primary router connection manually then don't trigger + any reconnect or backup router connections, by setting the router + to NULL here. */ + if (server->router == server_entry) { + server->id_entry->router = NULL; + server->router = NULL; + server->standalone = TRUE; + } + silc_server_free_sock_user_data(server, sock); + silc_server_close_connection(server, sock); + + out: + silc_server_command_free(cmd); } -SILC_SERVER_CMD_FUNC(oper) +/* Server side command of SHUTDOWN. Shutdowns the server and closes all + active connections. */ + +SILC_SERVER_CMD_FUNC(shutdown) { -} + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcClientEntry client = (SilcClientEntry)cmd->sock->user_data; -typedef struct { - char *channel_name; - char *nickname; - char *username; - char *hostname; - SilcChannelList *channel; - SilcServer server; -} JoinInternalContext; + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_SHUTDOWN, cmd, 0, 0); -SILC_TASK_CALLBACK(silc_server_command_join_notify) -{ - JoinInternalContext *ctx = (JoinInternalContext *)context; + if (!client || cmd->sock->type != SILC_SOCKET_TYPE_CLIENT) + goto out; - if (ctx->channel->key && ctx->channel->key_len) { - silc_server_send_notify_to_channel(ctx->server, ctx->channel, - "%s (%s@%s) has joined channel %s", - ctx->nickname, ctx->username, - ctx->hostname, ctx->channel_name); - silc_free(ctx); - } else { - silc_task_register(ctx->server->timeout_queue, fd, - silc_server_command_join_notify, context, - 0, 300000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + /* Check whether client has the permission. */ + if (client->mode == SILC_UMODE_NONE) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SHUTDOWN, + SILC_STATUS_ERR_NO_SERVER_PRIV); + goto out; } -} -/* Server side of command JOIN. Joins client into requested channel. If - the channel does not exist it will be created. */ + /* Send reply to the sender */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_SHUTDOWN, + SILC_STATUS_OK); -SILC_SERVER_CMD_FUNC(join) + /* Then, gracefully, or not, bring the server down. */ + silc_server_stop(server); + exit(0); + + out: + silc_server_command_free(cmd); +} + +/* Server side command of LEAVE. Removes client from a channel. */ + +SILC_SERVER_CMD_FUNC(leave) { SilcServerCommandContext cmd = (SilcServerCommandContext)context; SilcServer server = cmd->server; SilcSocketConnection sock = cmd->sock; - SilcBuffer buffer = cmd->packet->buffer; - int argc, i, tmp_len; - char *tmp, *channel_name = NULL, *cipher = NULL, *id_string = NULL; - unsigned char *passphrase; - SilcChannelList *channel; - SilcServerID *router_id; - SilcIDCache *id_cache; - SilcBuffer packet, sp_buf; - SilcClientList *client; - - SILC_LOG_DEBUG(("Start")); - -#define LCC(x) server->local_list->channel_cache[(x) - 32] -#define LCCC(x) server->local_list->channel_cache_count[(x) - 32] - - /* Check number of parameters */ - argc = silc_command_get_arg_num(cmd->payload); - if (argc < 1) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, - SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + SilcClientEntry id_entry = (SilcClientEntry)cmd->sock->user_data; + SilcChannelID *id = NULL; + SilcChannelEntry channel; + uint32 len; + unsigned char *tmp; + + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_LEAVE, cmd, 1, 2); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(cmd->args, 1, &len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE, + SILC_STATUS_ERR_NO_CHANNEL_ID); goto out; } - if (argc > 3) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, - SILC_STATUS_ERR_TOO_MANY_PARAMS); + id = silc_id_payload_parse_id(tmp, len); + if (!id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE, + SILC_STATUS_ERR_NO_CHANNEL_ID); goto out; } - /* Get channel name */ - tmp = silc_command_get_arg_type(cmd->payload, 1, NULL); - if (silc_server_command_bad_chars(tmp) == TRUE) { - silc_server_command_send_status_reply(cmd, SILC_COMMAND_JOIN, - SILC_STATUS_ERR_BAD_CHANNEL); - goto out; + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL); + if (!channel) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } } - channel_name = strdup(tmp); - /* Get passphrase */ - tmp = silc_command_get_arg_type(cmd->payload, 2, &tmp_len); - if (tmp) { - passphrase = silc_calloc(tmp_len, sizeof(*passphrase)); - memcpy(passphrase, tmp, tmp_len); + /* Check whether this client is on the channel */ + if (!silc_server_client_on_channel(id_entry, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE, + SILC_STATUS_ERR_NOT_ON_CHANNEL); + goto out; } - - /* Get cipher name */ - cipher = silc_command_get_arg_type(cmd->payload, 3, NULL); - /* See if the channel exists */ - if (silc_idcache_find_by_data(LCC(channel_name[0]), LCCC(channel_name[0]), - channel_name, &id_cache) == FALSE) { - /* Channel not found */ - id_cache = NULL; - - /* If we are standalone server we don't have a router, we just create - the channel by ourselves. */ - if (server->standalone) { - router_id = server->id; - channel = silc_server_new_channel(server, router_id, - cipher, channel_name); - goto join_channel; - } - - /* No channel ID found, the channel does not exist on our server. - We send JOIN command to our router which will handle the joining - procedure (either creates the channel if it doesn't exist or - joins the client to it) - if we are normal server. */ - if (server->server_type == SILC_SERVER) { - - /* Forward the received JOIN command to the router */ - silc_buffer_push(buffer, buffer->data - buffer->head); - silc_server_packet_forward(server, (SilcSocketConnection) - server->id_entry->router->connection, - buffer->data, buffer->len, - TRUE); - - /* Add the command to be pending. It will be re-executed after - router has replied back to us. */ - cmd->pending = TRUE; - silc_server_command_pending(SILC_COMMAND_JOIN, - silc_server_command_join, context); - return; - } - } + /* Notify routers that they should remove this client from their list + of clients on the channel. Send LEAVE notify type. */ + if (!server->standalone) + silc_server_send_notify_leave(server, server->router->connection, + server->server_type == SILC_ROUTER ? + TRUE : FALSE, channel, id_entry->id); - /* If we are router and the channel does not exist we will check our - global list for the channel. */ - if (!id_cache && server->server_type == SILC_ROUTER) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_LEAVE, + SILC_STATUS_OK); - /* Notify all routers about the new channel in SILC network. */ - if (!server->standalone) { -#if 0 - silc_server_send_new_id(server, server->id_entry->router->connection, - TRUE, - xxx, SILC_ID_CHANNEL, SILC_ID_CHANNEL_LEN); -#endif - } + /* Remove client from channel */ + if (!silc_server_remove_from_one_channel(server, sock, channel, id_entry, + TRUE)) + /* If the channel does not exist anymore we won't send anything */ + goto out; + + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + /* Re-generate channel key */ + if (!silc_server_create_channel_key(server, channel, 0)) + goto out; + /* Send the channel key */ + silc_server_send_channel_key(server, NULL, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); } - channel = (SilcChannelList *)id_cache->context; + out: + silc_free(id); + silc_server_command_free(cmd); +} - join_channel: +/* Server side of command USERS. Resolves clients and their USERS currently + joined on the requested channel. The list of Client ID's and their modes + on the channel is sent back. */ - /* XXX must check whether the client already is on the channel */ +SILC_SERVER_CMD_FUNC(users) +{ + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcChannelEntry channel; + SilcChannelID *id = NULL; + SilcBuffer packet, idp; + unsigned char *channel_id; + uint32 channel_id_len; + SilcBuffer client_id_list; + SilcBuffer client_mode_list; + unsigned char lc[4]; + uint32 list_count = 0; + uint16 ident = silc_command_get_ident(cmd->payload); + char *channel_name; - /* Join the client to the channel */ - i = channel->user_list_count; - channel->user_list = silc_realloc(channel->user_list, - sizeof(*channel->user_list) * (i + 1)); - channel->user_list[i].mode = SILC_CHANNEL_UMODE_NONE; + SILC_SERVER_COMMAND_CHECK(SILC_COMMAND_USERS, cmd, 1, 2); - /* If the JOIN request was forwarded to us we will make a bit slower - query to get the client pointer. Otherwise, we get the client pointer - real easy. */ - if (!(cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED)) { - client = (SilcClientList *)sock->user_data; - channel->user_list[i].client = client; - } else { - void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type); - client = silc_idlist_find_client_by_id(server->local_list->clients, id); - channel->user_list[i].client = client; - silc_free(id); - } - channel->user_list_count++; + /* Get Channel ID */ + channel_id = silc_argument_get_arg_type(cmd->args, 1, &channel_id_len); - i = client->channel_count; - client->channel = silc_realloc(client->channel, - sizeof(*client->channel) * (i + 1)); - client->channel[i] = channel; - client->channel_count++; + /* Get channel name */ + channel_name = silc_argument_get_arg_type(cmd->args, 2, NULL); - /* Notify router about new user on channel. If we are normal server - we send it to our router, if we are router we send it to our - primary route. */ - if (!server->standalone) { + if (!channel_id && !channel_name) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } + if (channel_id) { + id = silc_id_payload_parse_id(channel_id, channel_id_len); + if (!id) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS, + SILC_STATUS_ERR_NO_CHANNEL_ID); + goto out; + } } - /* Send Channel ID to the client */ - if (!cmd->pending) { - id_string = silc_id_id2str(channel->id, SILC_ID_CHANNEL); - sp_buf = silc_command_encode_status_payload(SILC_STATUS_OK, NULL, 0); - if (!channel->topic) - packet = - silc_command_encode_payload_va(SILC_COMMAND_JOIN, 3, - sp_buf->data, sp_buf->len, - channel_name, strlen(channel_name), - id_string, SILC_ID_CHANNEL_LEN); - else - packet = - silc_command_encode_payload_va(SILC_COMMAND_JOIN, 4, - sp_buf->data, sp_buf->len, - channel_name, strlen(channel_name), - id_string, SILC_ID_CHANNEL_LEN, - channel->topic, - strlen(channel->topic)); - - if (cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED) { - void *id = silc_id_str2id(cmd->packet->src_id, cmd->packet->src_id_type); - silc_server_packet_send_dest(cmd->server, cmd->sock, - SILC_PACKET_COMMAND_REPLY, 0, - id, cmd->packet->src_id_type, - packet->data, packet->len, FALSE); + /* If we are server and we don't know about this channel we will send + the command to our router. If we know about the channel then we also + have the list of users already. */ + if (id) + channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL); + else + channel = silc_idlist_find_channel_by_name(server->local_list, + channel_name, NULL); + + if (!channel || channel->disabled) { + if (server->server_type != SILC_ROUTER && !server->standalone && + !cmd->pending) { + SilcBuffer tmpbuf; + + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + /* Send USERS command */ + silc_server_packet_send(server, server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply */ + silc_server_command_pending(server, SILC_COMMAND_USERS, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_users, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + silc_command_set_ident(cmd->payload, ident); + + silc_buffer_free(tmpbuf); silc_free(id); - } else - silc_server_packet_send(server, sock, SILC_PACKET_COMMAND_REPLY, 0, - packet->data, packet->len, FALSE); - - silc_buffer_free(packet); - silc_free(sp_buf); - } + return; + } - /* Send channel key to the client. Client cannot start transmitting - to the channel until we have sent the key. */ - if (!cmd->pending) { - tmp_len = strlen(channel->channel_key->cipher->name); - packet = - silc_channel_key_encode_payload(SILC_ID_CHANNEL_LEN, - id_string, tmp_len, - channel->channel_key->cipher->name, - channel->key_len, channel->key); - - silc_server_packet_send(server, sock, SILC_PACKET_CHANNEL_KEY, 0, - packet->data, packet->len, FALSE); - silc_buffer_free(packet); + /* Check the global list as well. */ + if (id) + channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL); + else + channel = silc_idlist_find_channel_by_name(server->global_list, + channel_name, NULL); + if (!channel) { + /* Channel really does not exist */ + silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } } - if (id_string) - silc_free(id_string); - - /* Finally, send notify message to all clients on the channel about - new user on the channel. */ - if (!(cmd->packet->flags & SILC_PACKET_FLAG_FORWARDED)) { - if (!cmd->pending) { - silc_server_send_notify_to_channel(server, channel, - "%s (%s@%s) has joined channel %s", - client->nickname, client->username, - sock->hostname ? sock->hostname : - sock->ip, channel_name); - } else { - /* This is pending command request. Send the notify after we have - received the key for the channel from the router. */ - JoinInternalContext *ctx = silc_calloc(1, sizeof(*ctx)); - ctx->channel_name = channel_name; - ctx->nickname = client->nickname; - ctx->username = client->username; - ctx->hostname = sock->hostname ? sock->hostname : sock->ip; - ctx->channel = channel; - ctx->server = server; - silc_task_register(server->timeout_queue, sock->sock, - silc_server_command_join_notify, ctx, - 0, 100000, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + /* If the channel is private or secret do not send anything, unless the + user requesting this command is on the channel. */ + if (cmd->sock->type == SILC_SOCKET_TYPE_CLIENT) { + if (channel->mode & (SILC_CHANNEL_MODE_PRIVATE | SILC_CHANNEL_MODE_SECRET) + && !silc_server_client_on_channel(cmd->sock->user_data, channel)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; + } + } else { + if (channel->mode & + (SILC_CHANNEL_MODE_PRIVATE | SILC_CHANNEL_MODE_SECRET)) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_USERS, + SILC_STATUS_ERR_NO_SUCH_CHANNEL); + goto out; } } + /* Get the users list */ + silc_server_get_users_on_channel(server, channel, &client_id_list, + &client_mode_list, &list_count); + + /* List count */ + SILC_PUT32_MSB(list_count, lc); + + /* Send reply */ + idp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL); + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_USERS, + SILC_STATUS_OK, ident, 4, + 2, idp->data, idp->len, + 3, lc, 4, + 4, client_id_list->data, + client_id_list->len, + 5, client_mode_list->data, + client_mode_list->len); + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + + silc_buffer_free(idp); + silc_buffer_free(packet); + silc_buffer_free(client_id_list); + silc_buffer_free(client_mode_list); + silc_free(id); + out: silc_server_command_free(cmd); -#undef LCC -#undef LCCC } -/* Server side of command MOTD. Sends servers current "message of the - day" to the client. */ +/* Server side of command GETKEY. This fetches the client's public key + from the server where to the client is connected. */ -SILC_SERVER_CMD_FUNC(motd) +SILC_SERVER_CMD_FUNC(getkey) { + SilcServerCommandContext cmd = (SilcServerCommandContext)context; + SilcServer server = cmd->server; + SilcBuffer packet; + SilcClientEntry client; + SilcServerEntry server_entry; + SilcClientID *client_id = NULL; + SilcServerID *server_id = NULL; + SilcIDPayload idp = NULL; + uint16 ident = silc_command_get_ident(cmd->payload); + unsigned char *tmp, *pkdata; + uint32 tmp_len, pklen; + SilcBuffer pk = NULL; + SilcIdType id_type; SILC_LOG_DEBUG(("Start")); -} + tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + if (!tmp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } + idp = silc_id_payload_parse_data(tmp, tmp_len); + if (!idp) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY, + SILC_STATUS_ERR_NOT_ENOUGH_PARAMS); + goto out; + } -SILC_SERVER_CMD_FUNC(umode) -{ -} + id_type = silc_id_payload_get_type(idp); + if (id_type == SILC_ID_CLIENT) { + client_id = silc_id_payload_get_id(idp); + + /* If the client is not found from local list there is no chance it + would be locally connected client so send the command further. */ + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!client) + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + + if ((!client && !cmd->pending && !server->standalone) || + (client && !client->connection && !cmd->pending) || + (client && !client->data.public_key && !cmd->pending)) { + SilcBuffer tmpbuf; + uint16 old_ident; + SilcSocketConnection dest_sock; + + dest_sock = silc_server_get_client_route(server, NULL, 0, + client_id, NULL); + if (!dest_sock) + goto out; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + silc_server_packet_send(server, dest_sock, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_GETKEY, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_getkey, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_command_set_ident(cmd->payload, old_ident); + silc_buffer_free(tmpbuf); + return; + } -SILC_SERVER_CMD_FUNC(cmode) -{ -} + if (!client) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY, + SILC_STATUS_ERR_NO_SUCH_CLIENT_ID); + goto out; + } -SILC_SERVER_CMD_FUNC(kick) -{ -} + /* The client is locally connected, just get the public key and + send it back. If they key does not exist then do not send it, + send just OK reply */ + if (!client->data.public_key) { + pkdata = NULL; + pklen = 0; + } else { + tmp = silc_pkcs_public_key_encode(client->data.public_key, &tmp_len); + pk = silc_buffer_alloc(4 + tmp_len); + silc_buffer_pull_tail(pk, SILC_BUFFER_END(pk)); + silc_buffer_format(pk, + SILC_STR_UI_SHORT(tmp_len), + SILC_STR_UI_SHORT(SILC_SKE_PK_TYPE_SILC), + SILC_STR_UI_XNSTRING(tmp, tmp_len), + SILC_STR_END); + silc_free(tmp); + pkdata = pk->data; + pklen = pk->len; + } + } else if (id_type == SILC_ID_SERVER) { + server_id = silc_id_payload_get_id(idp); + + /* If the server is not found from local list there is no chance it + would be locally connected server so send the command further. */ + server_entry = silc_idlist_find_server_by_id(server->local_list, + server_id, TRUE, NULL); + if (!server_entry) + server_entry = silc_idlist_find_server_by_id(server->global_list, + server_id, TRUE, NULL); + + if (server_entry != server->id_entry && + ((!server_entry && !cmd->pending && !server->standalone) || + (server_entry && !server_entry->connection && !cmd->pending && + !server->standalone) || + (server_entry && !server_entry->data.public_key && !cmd->pending && + !server->standalone))) { + SilcBuffer tmpbuf; + uint16 old_ident; + + old_ident = silc_command_get_ident(cmd->payload); + silc_command_set_ident(cmd->payload, silc_rng_get_rn16(server->rng)); + tmpbuf = silc_command_payload_encode_payload(cmd->payload); + + silc_server_packet_send(server, server->router->connection, + SILC_PACKET_COMMAND, cmd->packet->flags, + tmpbuf->data, tmpbuf->len, TRUE); + + /* Reprocess this packet after received reply from router */ + silc_server_command_pending(server, SILC_COMMAND_GETKEY, + silc_command_get_ident(cmd->payload), + silc_server_command_destructor, + silc_server_command_getkey, + silc_server_command_dup(cmd)); + cmd->pending = TRUE; + + silc_command_set_ident(cmd->payload, old_ident); + silc_buffer_free(tmpbuf); + return; + } -SILC_SERVER_CMD_FUNC(restart) -{ -} - -SILC_SERVER_CMD_FUNC(close) -{ -} - -SILC_SERVER_CMD_FUNC(die) -{ -} - -SILC_SERVER_CMD_FUNC(silcoper) -{ -} + if (!server_entry) { + silc_server_command_send_status_reply(cmd, SILC_COMMAND_GETKEY, + SILC_STATUS_ERR_NO_SUCH_SERVER_ID); + goto out; + } -SILC_SERVER_CMD_FUNC(leave) -{ -} + /* If they key does not exist then do not send it, send just OK reply */ + if (!server_entry->data.public_key) { + pkdata = NULL; + pklen = 0; + } else { + tmp = silc_pkcs_public_key_encode(server_entry->data.public_key, + &tmp_len); + pk = silc_buffer_alloc(4 + tmp_len); + silc_buffer_pull_tail(pk, SILC_BUFFER_END(pk)); + silc_buffer_format(pk, + SILC_STR_UI_SHORT(tmp_len), + SILC_STR_UI_SHORT(SILC_SKE_PK_TYPE_SILC), + SILC_STR_UI_XNSTRING(tmp, tmp_len), + SILC_STR_END); + silc_free(tmp); + pkdata = pk->data; + pklen = pk->len; + } + } else { + goto out; + } -SILC_SERVER_CMD_FUNC(names) -{ + tmp = silc_argument_get_arg_type(cmd->args, 1, &tmp_len); + packet = silc_command_reply_payload_encode_va(SILC_COMMAND_GETKEY, + SILC_STATUS_OK, ident, + pkdata ? 2 : 1, + 2, tmp, tmp_len, + 3, pkdata, pklen); + silc_server_packet_send(server, cmd->sock, SILC_PACKET_COMMAND_REPLY, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + + if (pk) + silc_buffer_free(pk); + + out: + if (idp) + silc_id_payload_free(idp); + silc_free(client_id); + silc_free(server_id); + silc_server_command_free(cmd); } diff --git a/apps/silcd/command.h b/apps/silcd/command.h index d7ed4944..da7a3ea7 100644 --- a/apps/silcd/command.h +++ b/apps/silcd/command.h @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -52,25 +52,32 @@ typedef struct { SilcServer server; SilcSocketConnection sock; SilcCommandPayload payload; + SilcArgumentPayload args; SilcPacketContext *packet; - int pending; + int pending; /* Command is being re-processed when TRUE */ + int users; /* Reference counter */ } *SilcServerCommandContext; +/* Pending Command callback destructor. This is called after calling the + pending callback or if error occurs while processing the pending command. + If error occurs then the callback won't be called at all, and only this + destructor is called. The `context' is the context given for the function + silc_server_command_pending. */ +typedef void (*SilcServerPendingDestructor)(void *context); + /* Structure holding pending commands. If command is pending it will be - executed after command reply has been received and executed. - Pending commands are used in cases where the original command request - had to be forwarded to router. After router replies the pending - command is re-executed. */ + executed after command reply has been received and executed. */ typedef struct SilcServerCommandPendingStruct { + SilcServer server; SilcCommand reply_cmd; - void *context; SilcCommandCb callback; - + SilcServerPendingDestructor destructor; + void *context; + uint16 ident; struct SilcServerCommandPendingStruct *next; } SilcServerCommandPending; -/* List of pending commands */ -extern SilcServerCommandPending *silc_command_pending; +#include "command_reply.h" /* Macros */ @@ -78,60 +85,58 @@ extern SilcServerCommandPending *silc_command_pending; #define SILC_SERVER_CMD(func, cmd, flags) \ { silc_server_command_##func, SILC_COMMAND_##cmd, flags } -/* Macro used to declare command functions */ +/* Macro used to declare command functions. The `context' will be the + SilcServerCommandContext and the `context2' is the + SilcServerCommandReplyContext if this function is called from the + command reply as pending command callback. Otherwise `context2' + is NULL. */ #define SILC_SERVER_CMD_FUNC(func) \ -void silc_server_command_##func(void *context) - -/* Macro used to execute commands */ -#define SILC_SERVER_COMMAND_EXEC(ctx) \ -do { \ - SilcServerCommand *cmd; \ - \ - for (cmd = silc_command_list; cmd->cb; cmd++) \ - if (cmd->cmd == silc_command_get(ctx->payload)) { \ - cmd->cb(ctx); \ - break; \ - } \ - \ - if (cmd == NULL) { \ - SILC_LOG_ERROR(("Unknown command, packet dropped")); \ - silc_free(ctx); \ - return; \ - } \ -} while(0) - -/* Checks for pending commands */ -#define SILC_SERVER_COMMAND_CHECK_PENDING(ctx) \ -do { \ - if (silc_command_pending) { \ - SilcServerCommandPending *r; \ - SilcCommand cmd; \ - \ - cmd = silc_command_get(payload); \ - for (r = silc_command_pending; r; r = r->next) { \ - if (r->reply_cmd == cmd) { \ - ctx->context = r->context; \ - ctx->callback = r->callback; \ - break; \ - } \ - } \ - } \ +void silc_server_command_##func(void *context, void *context2) + +/* Executed pending command. The first argument to the callback function + is the user specified context. The second argument is always the + SilcServerCommandReply context. */ +#define SILC_SERVER_PENDING_EXEC(ctx, cmd) \ +do { \ + int _i; \ + for (_i = 0; _i < ctx->callbacks_count; _i++) \ + if (ctx->callbacks[_i].callback) \ + (*ctx->callbacks[_i].callback)(ctx->callbacks[_i].context, ctx); \ } while(0) -/* Executed pending command */ -#define SILC_SERVER_COMMAND_EXEC_PENDING(ctx, cmd) \ -do { \ - if (ctx->callback) { \ - (*ctx->callback)(ctx->context); \ - silc_server_command_pending_del(cmd); \ - } \ +/* Execute destructor for pending command */ +#define SILC_SERVER_PENDING_DESTRUCTOR(ctx, cmd) \ +do { \ + int _i; \ + silc_server_command_pending_del(ctx->server, cmd, ctx->ident); \ + for (_i = 0; _i < ctx->callbacks_count; _i++) \ + if (ctx->callbacks[_i].destructor) \ + (*ctx->callbacks[_i].destructor)(ctx->callbacks[_i].context); \ } while(0) /* Prototypes */ -void silc_server_command_pending(SilcCommand reply_cmd, +void silc_server_command_process(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +SilcServerCommandContext silc_server_command_alloc(); +void silc_server_command_free(SilcServerCommandContext ctx); +SilcServerCommandContext +silc_server_command_dup(SilcServerCommandContext ctx); +void silc_server_command_pending(SilcServer server, + SilcCommand reply_cmd, + uint16 ident, + SilcServerPendingDestructor destructor, SilcCommandCb callback, void *context); -void silc_server_command_pending_del(SilcCommand reply_cmd); +void silc_server_command_pending_del(SilcServer server, + SilcCommand reply_cmd, + uint16 ident); +SilcServerCommandPendingCallbacks +silc_server_command_pending_check(SilcServer server, + SilcServerCommandReplyContext ctx, + SilcCommand command, + uint16 ident, + uint32 *callbacks_count); SILC_SERVER_CMD_FUNC(whois); SILC_SERVER_CMD_FUNC(whowas); SILC_SERVER_CMD_FUNC(identify); @@ -152,13 +157,15 @@ SILC_SERVER_CMD_FUNC(join); SILC_SERVER_CMD_FUNC(motd); SILC_SERVER_CMD_FUNC(umode); SILC_SERVER_CMD_FUNC(cmode); +SILC_SERVER_CMD_FUNC(cumode); SILC_SERVER_CMD_FUNC(kick); SILC_SERVER_CMD_FUNC(ignore); -SILC_SERVER_CMD_FUNC(restart); +SILC_SERVER_CMD_FUNC(ban); SILC_SERVER_CMD_FUNC(close); -SILC_SERVER_CMD_FUNC(die); +SILC_SERVER_CMD_FUNC(shutdown); SILC_SERVER_CMD_FUNC(silcoper); SILC_SERVER_CMD_FUNC(leave); -SILC_SERVER_CMD_FUNC(names); +SILC_SERVER_CMD_FUNC(users); +SILC_SERVER_CMD_FUNC(getkey); #endif diff --git a/apps/silcd/command_reply.c b/apps/silcd/command_reply.c index 815a8585..cb6c6973 100644 --- a/apps/silcd/command_reply.c +++ b/apps/silcd/command_reply.c @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -17,25 +17,46 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "serverincludes.h" #include "server_internal.h" #include "command_reply.h" +/* All functions that call the COMMAND_CHECK_STATUS or the + COMMAND_CHECK_STATUS_LIST macros must have out: goto label. */ + +#define COMMAND_CHECK_STATUS \ +do { \ + SILC_LOG_DEBUG(("Start")); \ + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); \ + if (status != SILC_STATUS_OK) \ + goto out; \ +} while(0) + +#define COMMAND_CHECK_STATUS_LIST \ +do { \ + SILC_LOG_DEBUG(("Start")); \ + SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); \ + if (status != SILC_STATUS_OK && \ + status != SILC_STATUS_LIST_START && \ + status != SILC_STATUS_LIST_ITEM && \ + status != SILC_STATUS_LIST_END) \ + goto out; \ +} while(0) + /* Server command reply list. Not all commands have reply function as - they are never sent as forwarded command packets by server. More - maybe added later if need appears. */ + they are never sent by server. More maybe added later if need appears. */ SilcServerCommandReply silc_command_reply_list[] = { + SILC_SERVER_CMD_REPLY(whois, WHOIS), + SILC_SERVER_CMD_REPLY(whowas, WHOWAS), + SILC_SERVER_CMD_REPLY(identify, IDENTIFY), + SILC_SERVER_CMD_REPLY(info, INFO), + SILC_SERVER_CMD_REPLY(motd, MOTD), SILC_SERVER_CMD_REPLY(join, JOIN), + SILC_SERVER_CMD_REPLY(users, USERS), + SILC_SERVER_CMD_REPLY(getkey, GETKEY), { NULL, 0 }, }; @@ -46,11 +67,16 @@ void silc_server_command_reply_process(SilcServer server, SilcSocketConnection sock, SilcBuffer buffer) { + SilcServerCommandReply *cmd; SilcServerCommandReplyContext ctx; SilcCommandPayload payload; + SilcCommand command; + uint16 ident; + + SILC_LOG_DEBUG(("Start")); /* Get command reply payload from packet */ - payload = silc_command_parse_payload(buffer); + payload = silc_command_payload_parse(buffer); if (!payload) { /* Silently ignore bad reply packet */ SILC_LOG_DEBUG(("Bad command reply packet")); @@ -61,14 +87,29 @@ void silc_server_command_reply_process(SilcServer server, command reply routine receiving it. */ ctx = silc_calloc(1, sizeof(*ctx)); ctx->server = server; - ctx->sock = sock; + ctx->sock = silc_socket_dup(sock); ctx->payload = payload; + ctx->args = silc_command_get_args(ctx->payload); + ident = silc_command_get_ident(ctx->payload); /* Check for pending commands and mark to be exeucted */ - SILC_SERVER_COMMAND_CHECK_PENDING(ctx); - + ctx->callbacks = + silc_server_command_pending_check(server, ctx, + silc_command_get(ctx->payload), + ident, &ctx->callbacks_count); + /* Execute command reply */ - SILC_SERVER_COMMAND_REPLY_EXEC(ctx); + command = silc_command_get(ctx->payload); + for (cmd = silc_command_reply_list; cmd->cb; cmd++) + if (cmd->cmd == command) + break; + + if (cmd == NULL || !cmd->cb) { + silc_server_command_reply_free(ctx); + return; + } + + cmd->cb(ctx, NULL); } /* Free command reply context and its internals. */ @@ -76,11 +117,580 @@ void silc_server_command_reply_process(SilcServer server, void silc_server_command_reply_free(SilcServerCommandReplyContext cmd) { if (cmd) { - silc_command_free_payload(cmd->payload); + silc_command_payload_free(cmd->payload); + if (cmd->sock) + silc_socket_free(cmd->sock); /* Decrease the reference counter */ + silc_free(cmd->callbacks); silc_free(cmd); } } +/* Caches the received WHOIS information. */ + +static char +silc_server_command_reply_whois_save(SilcServerCommandReplyContext cmd) +{ + SilcServer server = cmd->server; + unsigned char *tmp, *id_data; + char *nickname, *username, *realname, *servername = NULL; + SilcClientID *client_id; + SilcClientEntry client; + char global = FALSE; + char *nick; + uint32 mode = 0, len, id_len; + + id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len); + nickname = silc_argument_get_arg_type(cmd->args, 3, &len); + username = silc_argument_get_arg_type(cmd->args, 4, &len); + realname = silc_argument_get_arg_type(cmd->args, 5, &len); + if (!id_data || !nickname || !username || !realname) { + SILC_LOG_ERROR(("Incomplete WHOIS info: %s %s %s", + nickname ? nickname : "", + username ? username : "", + realname ? realname : "")); + return FALSE; + } + + tmp = silc_argument_get_arg_type(cmd->args, 7, &len); + if (tmp) + SILC_GET32_MSB(mode, tmp); + + client_id = silc_id_payload_parse_id(id_data, id_len); + if (!client_id) + return FALSE; + + /* Check if we have this client cached already. */ + + client = silc_idlist_find_client_by_id(server->local_list, client_id, + FALSE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->global_list, client_id, + FALSE, NULL); + global = TRUE; + } + + if (!client) { + /* If router did not find such Client ID in its lists then this must + be bogus client or some router in the net is buggy. */ + if (server->server_type != SILC_SERVER) + return FALSE; + + /* Take hostname out of nick string if it includes it. */ + silc_parse_userfqdn(nickname, &nick, &servername); + + /* We don't have that client anywhere, add it. The client is added + to global list since server didn't have it in the lists so it must be + global. */ + client = silc_idlist_add_client(server->global_list, nick, + strdup(username), + strdup(realname), client_id, + cmd->sock->user_data, NULL); + if (!client) { + SILC_LOG_ERROR(("Could not add new client to the ID Cache")); + return FALSE; + } + + client->data.status |= + (SILC_IDLIST_STATUS_REGISTERED | SILC_IDLIST_STATUS_RESOLVED); + client->data.status &= ~SILC_IDLIST_STATUS_RESOLVING; + client->mode = mode; + client->servername = servername; + } else { + /* We have the client already, update the data */ + + SILC_LOG_DEBUG(("Updating client data")); + + /* Take hostname out of nick string if it includes it. */ + silc_parse_userfqdn(nickname, &nick, &servername); + + /* Remove the old cache entry */ + silc_idcache_del_by_context(global ? server->global_list->clients : + server->local_list->clients, client); + + silc_free(client->nickname); + silc_free(client->username); + silc_free(client->userinfo); + silc_free(client->servername); + + client->nickname = nick; + client->username = strdup(username); + client->userinfo = strdup(realname); + client->servername = servername; + client->mode = mode; + client->data.status |= SILC_IDLIST_STATUS_RESOLVED; + client->data.status &= ~SILC_IDLIST_STATUS_RESOLVING; + + /* Create new cache entry */ + silc_idcache_add(global ? server->global_list->clients : + server->local_list->clients, nick, client->id, + client, FALSE); + silc_free(client_id); + } + + return TRUE; +} + +/* Reiceved reply for WHOIS command. We sent the whois request to our + primary router, if we are normal server, and thus has now received reply + to the command. We will figure out what client originally sent us the + command and will send the reply to it. If we are router we will figure + out who server sent us the command and send reply to that one. */ + +SILC_SERVER_CMD_REPLY_FUNC(whois) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcCommandStatus status; + + COMMAND_CHECK_STATUS_LIST; + + if (!silc_server_command_reply_whois_save(cmd)) + goto out; + + /* Pending callbacks are not executed if this was an list entry */ + if (status != SILC_STATUS_OK && + status != SILC_STATUS_LIST_END) { + silc_server_command_reply_free(cmd); + return; + } + + out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_WHOIS); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_WHOIS); + silc_server_command_reply_free(cmd); +} + +/* Caches the received WHOWAS information for a short period of time. */ + +static char +silc_server_command_reply_whowas_save(SilcServerCommandReplyContext cmd) +{ + SilcServer server = cmd->server; + uint32 len, id_len; + unsigned char *id_data; + char *nickname, *username, *realname, *servername = NULL; + SilcClientID *client_id; + SilcClientEntry client; + SilcIDCacheEntry cache = NULL; + char *nick; + int global = FALSE; + + id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len); + nickname = silc_argument_get_arg_type(cmd->args, 3, &len); + username = silc_argument_get_arg_type(cmd->args, 4, &len); + if (!id_data || !nickname || !username) + return FALSE; + + realname = silc_argument_get_arg_type(cmd->args, 5, &len); + + client_id = silc_id_payload_parse_id(id_data, id_len); + if (!client_id) + return FALSE; + + /* Check if we have this client cached already. */ + + client = silc_idlist_find_client_by_id(server->local_list, client_id, + FALSE, &cache); + if (!client) { + client = silc_idlist_find_client_by_id(server->global_list, + client_id, FALSE, &cache); + global = TRUE; + } + + if (!client) { + /* If router did not find such Client ID in its lists then this must + be bogus client or some router in the net is buggy. */ + if (server->server_type != SILC_SERVER) + return FALSE; + + /* Take hostname out of nick string if it includes it. */ + silc_parse_userfqdn(nickname, &nick, &servername); + + /* We don't have that client anywhere, add it. The client is added + to global list since server didn't have it in the lists so it must be + global. */ + client = silc_idlist_add_client(server->global_list, nick, + strdup(username), strdup(realname), + silc_id_dup(client_id, SILC_ID_CLIENT), + cmd->sock->user_data, NULL); + if (!client) { + SILC_LOG_ERROR(("Could not add new client to the ID Cache")); + return FALSE; + } + + client->data.status &= ~SILC_IDLIST_STATUS_REGISTERED; + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, &cache); + cache->expire = SILC_ID_CACHE_EXPIRE_DEF; + client->servername = servername; + } else { + /* We have the client already, update the data */ + + /* Take hostname out of nick string if it includes it. */ + silc_parse_userfqdn(nickname, &nick, &servername); + + silc_free(client->nickname); + silc_free(client->username); + + client->nickname = nick; + client->username = strdup(username); + client->servername = servername; + + /* Remove the old cache entry and create a new one */ + silc_idcache_del_by_context(global ? server->global_list->clients : + server->local_list->clients, client); + silc_idcache_add(global ? server->global_list->clients : + server->local_list->clients, nick, client->id, + client, FALSE); + } + + silc_free(client_id); + + return TRUE; +} + +/* Received reply for WHOWAS command. Cache the client information only for + a short period of time. */ + +SILC_SERVER_CMD_REPLY_FUNC(whowas) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcCommandStatus status; + + COMMAND_CHECK_STATUS_LIST; + + if (!silc_server_command_reply_whowas_save(cmd)) + goto out; + + /* Pending callbacks are not executed if this was an list entry */ + if (status != SILC_STATUS_OK && + status != SILC_STATUS_LIST_END) { + silc_server_command_reply_free(cmd); + return; + } + + out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_WHOWAS); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_WHOWAS); + silc_server_command_reply_free(cmd); +} + +/* Caches the received IDENTIFY information. */ + +static char +silc_server_command_reply_identify_save(SilcServerCommandReplyContext cmd) +{ + SilcServer server = cmd->server; + uint32 len, id_len; + unsigned char *id_data; + char *name, *info; + SilcClientID *client_id = NULL; + SilcServerID *server_id = NULL; + SilcChannelID *channel_id = NULL; + SilcClientEntry client; + SilcServerEntry server_entry; + SilcChannelEntry channel; + char global = FALSE; + char *nick = NULL; + SilcIDPayload idp = NULL; + SilcIdType id_type; + + id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len); + if (!id_data) + return FALSE; + idp = silc_id_payload_parse_data(id_data, id_len); + if (!idp) + return FALSE; + + name = silc_argument_get_arg_type(cmd->args, 3, &len); + info = silc_argument_get_arg_type(cmd->args, 4, &len); + + id_type = silc_id_payload_get_type(idp); + + switch (id_type) { + case SILC_ID_CLIENT: + client_id = silc_id_payload_get_id(idp); + if (!client_id) + goto error; + + SILC_LOG_DEBUG(("Received client information")); + + client = silc_idlist_find_client_by_id(server->local_list, + client_id, FALSE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->global_list, client_id, + FALSE, NULL); + global = TRUE; + } + if (!client) { + /* If router did not find such Client ID in its lists then this must + be bogus client or some router in the net is buggy. */ + if (server->server_type != SILC_SERVER) + goto error; + + /* Take nickname */ + if (name) + silc_parse_userfqdn(name, &nick, NULL); + + /* We don't have that client anywhere, add it. The client is added + to global list since server didn't have it in the lists so it must be + global. */ + client = silc_idlist_add_client(server->global_list, nick, + info ? strdup(info) : NULL, NULL, + client_id, cmd->sock->user_data, NULL); + if (!client) { + SILC_LOG_ERROR(("Could not add new client to the ID Cache")); + goto error; + } + client->data.status |= SILC_IDLIST_STATUS_REGISTERED; + client->data.status |= SILC_IDLIST_STATUS_RESOLVED; + client->data.status &= ~SILC_IDLIST_STATUS_RESOLVING; + } else { + /* We have the client already, update the data */ + + SILC_LOG_DEBUG(("Updating client data")); + + /* Take nickname */ + if (name) { + silc_parse_userfqdn(name, &nick, NULL); + + /* Remove the old cache entry */ + silc_idcache_del_by_context(global ? server->global_list->clients : + server->local_list->clients, client); + + silc_free(client->nickname); + client->nickname = nick; + } + + if (info) { + silc_free(client->username); + client->username = strdup(info); + } + + client->data.status |= SILC_IDLIST_STATUS_RESOLVED; + client->data.status &= ~SILC_IDLIST_STATUS_RESOLVING; + + if (name) { + /* Add new cache entry */ + silc_idcache_add(global ? server->global_list->clients : + server->local_list->clients, nick, client->id, + client, FALSE); + } + + silc_free(client_id); + } + + break; + + case SILC_ID_SERVER: + server_id = silc_id_payload_get_id(idp); + if (!server_id) + goto error; + + SILC_LOG_DEBUG(("Received server information")); + + server_entry = silc_idlist_find_server_by_id(server->local_list, + server_id, FALSE, NULL); + if (!server_entry) + server_entry = silc_idlist_find_server_by_id(server->global_list, + server_id, FALSE, NULL); + if (!server_entry) { + /* If router did not find such Server ID in its lists then this must + be bogus server or some router in the net is buggy. */ + if (server->server_type != SILC_SERVER) + goto error; + + /* We don't have that server anywhere, add it. */ + server_entry = silc_idlist_add_server(server->global_list, + strdup(name), 0, + server_id, NULL, NULL); + if (!server_entry) { + silc_free(server_id); + goto error; + } + server_entry->data.status |= SILC_IDLIST_STATUS_REGISTERED; + server_entry->data.status |= SILC_IDLIST_STATUS_RESOLVED; + server_entry->data.status &= ~SILC_IDLIST_STATUS_RESOLVING; + server_id = NULL; + } + + silc_free(server_id); + break; + + case SILC_ID_CHANNEL: + channel_id = silc_id_payload_get_id(idp); + if (!channel_id) + goto error; + + SILC_LOG_DEBUG(("Received channel information")); + + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) + channel = silc_idlist_find_channel_by_id(server->global_list, channel_id, + NULL); + if (!channel) { + /* If router did not find such Channel ID in its lists then this must + be bogus channel or some router in the net is buggy. */ + if (server->server_type != SILC_SERVER) + goto error; + + /* We don't have that server anywhere, add it. */ + channel = silc_idlist_add_channel(server->global_list, strdup(name), + SILC_CHANNEL_MODE_NONE, channel_id, + server->router->connection, + NULL, NULL); + if (!channel) { + silc_free(channel_id); + goto error; + } + channel_id = NULL; + } + + silc_free(channel_id); + break; + } + + silc_id_payload_free(idp); + return TRUE; + + error: + silc_id_payload_free(idp); + return FALSE; +} + +/* Received reply for forwarded IDENTIFY command. We have received the + requested identify information now and we will cache it. After this we + will call the pending command so that the requestee gets the information + after all. */ + +SILC_SERVER_CMD_REPLY_FUNC(identify) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcCommandStatus status; + + COMMAND_CHECK_STATUS_LIST; + + if (!silc_server_command_reply_identify_save(cmd)) + goto out; + + /* Pending callbacks are not executed if this was an list entry */ + if (status != SILC_STATUS_OK && + status != SILC_STATUS_LIST_END) { + silc_server_command_reply_free(cmd); + return; + } + + out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_IDENTIFY); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_IDENTIFY); + silc_server_command_reply_free(cmd); +} + +/* Received reply fro INFO command. Cache the server and its information */ + +SILC_SERVER_CMD_REPLY_FUNC(info) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcServer server = cmd->server; + SilcCommandStatus status; + SilcServerEntry entry; + SilcServerID *server_id; + uint32 tmp_len; + unsigned char *tmp, *name; + + COMMAND_CHECK_STATUS; + + /* Get Server ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!tmp) + goto out; + server_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!server_id) + goto out; + + /* Get the name */ + name = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + if (tmp_len > 256) + goto out; + + entry = silc_idlist_find_server_by_id(server->local_list, server_id, + FALSE, NULL); + if (!entry) { + entry = silc_idlist_find_server_by_id(server->global_list, server_id, + FALSE, NULL); + if (!entry) { + /* Add the server to global list */ + server_id = silc_id_dup(server_id, SILC_ID_SERVER); + entry = silc_idlist_add_server(server->global_list, name, 0, + server_id, NULL, NULL); + if (!entry) { + silc_free(server_id); + goto out; + } + entry->data.status |= SILC_IDLIST_STATUS_REGISTERED; + } + } + + /* Get the info string */ + tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len); + if (tmp_len > 256) + tmp = NULL; + + entry->server_info = tmp ? strdup(tmp) : NULL; + + out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_INFO); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_INFO); + silc_server_command_reply_free(cmd); +} + +/* Received reply fro MOTD command. */ + +SILC_SERVER_CMD_REPLY_FUNC(motd) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcServer server = cmd->server; + SilcCommandStatus status; + SilcServerEntry entry = NULL; + SilcServerID *server_id; + uint32 tmp_len; + unsigned char *tmp; + + COMMAND_CHECK_STATUS; + + /* Get Server ID */ + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!tmp) + goto out; + server_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!server_id) + goto out; + + entry = silc_idlist_find_server_by_id(server->local_list, server_id, + TRUE, NULL); + if (!entry) { + entry = silc_idlist_find_server_by_id(server->global_list, server_id, + TRUE, NULL); + if (!entry) + goto out; + } + + /* Get the motd */ + tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + if (tmp_len > 256) + tmp = NULL; + + entry->motd = tmp; + + out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_MOTD); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_MOTD); + silc_server_command_reply_free(cmd); + + if (entry) + entry->motd = NULL; +} + /* Received reply for forwarded JOIN command. Router has created or joined the client to the channel. We save some channel information locally for future use. */ @@ -89,52 +699,379 @@ SILC_SERVER_CMD_REPLY_FUNC(join) { SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; SilcServer server = cmd->server; + SilcIDCacheEntry cache = NULL; SilcCommandStatus status; SilcChannelID *id; - SilcChannelList *entry; - unsigned int argc; + SilcClientID *client_id = NULL; + SilcChannelEntry entry; + SilcHmac hmac = NULL; + uint32 id_len, len, list_count; unsigned char *id_string; char *channel_name, *tmp; + uint32 mode, created; + SilcBuffer keyp = NULL, client_id_list = NULL, client_mode_list = NULL; -#define LCC(x) server->local_list->channel_cache[(x) - 32] -#define LCCC(x) server->local_list->channel_cache_count[(x) - 32] + COMMAND_CHECK_STATUS; - SILC_LOG_DEBUG(("Start")); + /* Get channel name */ + channel_name = silc_argument_get_arg_type(cmd->args, 2, NULL); + if (!channel_name) + goto out; - tmp = silc_command_get_arg_type(cmd->payload, 1, NULL); - SILC_GET16_MSB(status, tmp); - if (status != SILC_STATUS_OK) + /* Get channel ID */ + id_string = silc_argument_get_arg_type(cmd->args, 3, &id_len); + if (!id_string) goto out; - /* Get channel name */ - tmp = silc_command_get_arg_type(cmd->payload, 2, NULL); + /* Get client ID */ + tmp = silc_argument_get_arg_type(cmd->args, 4, &len); + if (!tmp) + goto out; + client_id = silc_id_payload_parse_id(tmp, len); + if (!client_id) + goto out; + + /* Get mode mask */ + tmp = silc_argument_get_arg_type(cmd->args, 5, NULL); + if (!tmp) + goto out; + SILC_GET32_MSB(mode, tmp); + + /* Get created boolean value */ + tmp = silc_argument_get_arg_type(cmd->args, 6, NULL); + if (!tmp) + goto out; + SILC_GET32_MSB(created, tmp); + if (created != 0 && created != 1) + goto out; + + /* Get channel key */ + tmp = silc_argument_get_arg_type(cmd->args, 7, &len); + if (tmp) { + keyp = silc_buffer_alloc(len); + silc_buffer_pull_tail(keyp, SILC_BUFFER_END(keyp)); + silc_buffer_put(keyp, tmp, len); + } + + id = silc_id_payload_parse_id(id_string, id_len); + if (!id) + goto out; + + /* Get hmac */ + tmp = silc_argument_get_arg_type(cmd->args, 11, NULL); + if (tmp) { + if (!silc_hmac_alloc(tmp, NULL, &hmac)) + goto out; + } + + /* Get the list count */ + tmp = silc_argument_get_arg_type(cmd->args, 12, &len); + if (!tmp) + goto out; + SILC_GET32_MSB(list_count, tmp); + + /* Get Client ID list */ + tmp = silc_argument_get_arg_type(cmd->args, 13, &len); + if (!tmp) + goto out; + + client_id_list = silc_buffer_alloc(len); + silc_buffer_pull_tail(client_id_list, len); + silc_buffer_put(client_id_list, tmp, len); + + /* Get client mode list */ + tmp = silc_argument_get_arg_type(cmd->args, 14, &len); if (!tmp) goto out; + client_mode_list = silc_buffer_alloc(len); + silc_buffer_pull_tail(client_mode_list, len); + silc_buffer_put(client_mode_list, tmp, len); + + /* See whether we already have the channel. */ + entry = silc_idlist_find_channel_by_name(server->local_list, + channel_name, &cache); + if (!entry) { + /* Add new channel */ + + SILC_LOG_DEBUG(("Adding new [%s] channel %s id(%s)", + (created == 0 ? "existing" : "created"), channel_name, + silc_id_render(id, SILC_ID_CHANNEL))); + + /* If the channel is found from global list we must move it to the + local list. */ + entry = silc_idlist_find_channel_by_name(server->global_list, + channel_name, &cache); + if (entry) + silc_idlist_del_channel(server->global_list, entry); + + /* Add the channel to our local list. */ + entry = silc_idlist_add_channel(server->local_list, strdup(channel_name), + SILC_CHANNEL_MODE_NONE, id, + server->router, NULL, hmac); + if (!entry) { + silc_free(id); + goto out; + } + server->stat.my_channels++; + } else { + /* The entry exists. */ + silc_free(cache->id); + entry->id = id; + cache->id = entry->id; + entry->disabled = FALSE; + + /* Remove the founder auth data if the mode is not set but we have + them in the entry */ + if (!(mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) && entry->founder_key) { + silc_pkcs_public_key_free(entry->founder_key); + if (entry->founder_passwd) { + silc_free(entry->founder_passwd); + entry->founder_passwd = NULL; + } + } + } + + if (entry->hmac_name && hmac) { + silc_free(entry->hmac_name); + entry->hmac_name = strdup(silc_hmac_get_name(hmac)); + } + + /* Get the ban list */ + tmp = silc_argument_get_arg_type(cmd->args, 8, &len); + if (tmp) { + if (entry->ban_list) + silc_free(entry->ban_list); + entry->ban_list = silc_calloc(len, sizeof(*entry->ban_list)); + memcpy(entry->ban_list, tmp, len); + } + + /* Get the invite list */ + tmp = silc_argument_get_arg_type(cmd->args, 9, &len); + if (tmp) { + if (entry->invite_list) + silc_free(entry->invite_list); + entry->invite_list = silc_calloc(len, sizeof(*entry->invite_list)); + memcpy(entry->invite_list, tmp, len); + } + + /* Get the topic */ + tmp = silc_argument_get_arg_type(cmd->args, 10, &len); + if (tmp) { + if (entry->topic) + silc_free(entry->topic); + entry->topic = strdup(tmp); + } + + /* If channel was not created we know there is global users on the + channel. */ + entry->global_users = (created == 0 ? TRUE : FALSE); + + /* If channel was just created the mask must be zero */ + if (!entry->global_users && mode) { + SILC_LOG_DEBUG(("Buggy router `%s' sent non-zero mode mask for " + "new channel, forcing it to zero", cmd->sock->hostname)); + mode = 0; + } + + /* Save channel mode */ + entry->mode = mode; + + /* Save channel key */ + if (!(entry->mode & SILC_CHANNEL_MODE_PRIVKEY)) + silc_server_save_channel_key(server, keyp, entry); + if (keyp) + silc_buffer_free(keyp); + + /* Save the users to the channel */ + silc_server_save_users_on_channel(server, cmd->sock, entry, + client_id, client_id_list, + client_mode_list, list_count); + + out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_JOIN); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_JOIN); + silc_free(client_id); + silc_server_command_reply_free(cmd); + + if (client_id_list) + silc_buffer_free(client_id_list); + if (client_mode_list) + silc_buffer_free(client_mode_list); +} + +SILC_SERVER_CMD_REPLY_FUNC(users) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcServer server = cmd->server; + SilcCommandStatus status; + SilcChannelEntry channel; + SilcChannelID *channel_id = NULL; + SilcBuffer client_id_list; + SilcBuffer client_mode_list; + unsigned char *tmp; + uint32 tmp_len; + uint32 list_count; + + COMMAND_CHECK_STATUS; + /* Get channel ID */ - id_string = silc_command_get_arg_type(cmd->payload, 3, NULL); - if (!id_string) + tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len); + if (!tmp) + goto out; + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id) + goto out; + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + SilcBuffer idp; + + if (server->server_type != SILC_SERVER) + goto out; + + idp = silc_id_payload_encode(channel_id, SILC_ID_CHANNEL); + silc_server_send_command(server, server->router->connection, + SILC_COMMAND_IDENTIFY, ++server->cmd_ident, + 1, 5, idp->data, idp->len); + silc_buffer_free(idp); + + /* Register pending command callback. After we've received the channel + information we will reprocess this command reply by re-calling this + USERS command reply callback. */ + silc_server_command_pending(server, SILC_COMMAND_IDENTIFY, + server->cmd_ident, + NULL, silc_server_command_reply_users, cmd); + return; + } + } + + /* Get the list count */ + tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len); + if (!tmp) goto out; + SILC_GET32_MSB(list_count, tmp); - channel_name = strdup(tmp); + /* Get Client ID list */ + tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len); + if (!tmp) + goto out; - /* Add the channel to our local list. */ - id = silc_id_str2id(id_string, SILC_ID_CHANNEL); - silc_idlist_add_channel(&server->local_list->channels, channel_name, - SILC_CHANNEL_MODE_NONE, id, - server->id_entry->router, NULL, &entry); - LCCC(channel_name[0]) = silc_idcache_add(&LCC(channel_name[0]), - LCCC(channel_name[0]), - channel_name, SILC_ID_CHANNEL, - (void *)id, (void *)entry); - entry->global_users = TRUE; + client_id_list = silc_buffer_alloc(tmp_len); + silc_buffer_pull_tail(client_id_list, tmp_len); + silc_buffer_put(client_id_list, tmp, tmp_len); - /* Execute pending JOIN command so that the client who originally - wanted to join the channel will be joined after all. */ - SILC_SERVER_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_JOIN); + /* Get client mode list */ + tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len); + if (!tmp) + goto out; + + client_mode_list = silc_buffer_alloc(tmp_len); + silc_buffer_pull_tail(client_mode_list, tmp_len); + silc_buffer_put(client_mode_list, tmp, tmp_len); + + /* Save the users to the channel */ + silc_server_save_users_on_channel(server, cmd->sock, channel, NULL, + client_id_list, client_mode_list, + list_count); + + silc_buffer_free(client_id_list); + silc_buffer_free(client_mode_list); + + out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_USERS); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_USERS); + silc_free(channel_id); + silc_server_command_reply_free(cmd); +} + +SILC_SERVER_CMD_REPLY_FUNC(getkey) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcServer server = cmd->server; + SilcCommandStatus status; + SilcClientEntry client = NULL; + SilcServerEntry server_entry = NULL; + SilcClientID *client_id = NULL; + SilcServerID *server_id = NULL; + SilcSKEPKType type; + unsigned char *tmp, *pk; + uint32 len; + uint16 pk_len; + SilcIDPayload idp = NULL; + SilcIdType id_type; + SilcPublicKey public_key = NULL; + + COMMAND_CHECK_STATUS; + + tmp = silc_argument_get_arg_type(cmd->args, 2, &len); + if (!tmp) + goto out; + idp = silc_id_payload_parse_data(tmp, len); + if (!idp) + goto out; + + /* Get the public key payload */ + tmp = silc_argument_get_arg_type(cmd->args, 3, &len); + if (!tmp) + goto out; + + /* Decode the public key */ + + SILC_GET16_MSB(pk_len, tmp); + SILC_GET16_MSB(type, tmp + 2); + pk = tmp + 4; + + if (type != SILC_SKE_PK_TYPE_SILC) + goto out; + + if (!silc_pkcs_public_key_decode(pk, pk_len, &public_key)) + goto out; + + id_type = silc_id_payload_get_type(idp); + if (id_type == SILC_ID_CLIENT) { + client_id = silc_id_payload_get_id(idp); + + client = silc_idlist_find_client_by_id(server->local_list, client_id, + TRUE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!client) + goto out; + } + + client->data.public_key = public_key; + } else if (id_type == SILC_ID_SERVER) { + server_id = silc_id_payload_get_id(idp); + + server_entry = silc_idlist_find_server_by_id(server->local_list, server_id, + TRUE, NULL); + if (!server_entry) { + server_entry = silc_idlist_find_server_by_id(server->global_list, + server_id, TRUE, NULL); + if (!server_entry) + goto out; + } + + server_entry->data.public_key = public_key; + } else { + goto out; + } out: + SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_USERS); + SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_USERS); + if (idp) + silc_id_payload_free(idp); + silc_free(client_id); + silc_free(server_id); + if (public_key) + silc_pkcs_public_key_free(public_key); silc_server_command_reply_free(cmd); -#undef LCC -#undef LCCC } diff --git a/apps/silcd/command_reply.h b/apps/silcd/command_reply.h index 0e67aa28..eb5b53d4 100644 --- a/apps/silcd/command_reply.h +++ b/apps/silcd/command_reply.h @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -21,6 +21,8 @@ #ifndef COMMAND_REPLY_H #define COMMAND_REPLY_H +#include "command.h" + /* Structure holding one command reply and pointer to its function. */ typedef struct { SilcCommandCb cb; @@ -30,15 +32,24 @@ typedef struct { /* All server command replys */ extern SilcServerCommandReply silc_command_reply_list[]; +/* Context holding pending command callbacks. */ +typedef struct { + SilcServerPendingDestructor destructor; + SilcCommandCb callback; + void *context; +} *SilcServerCommandPendingCallbacks; + /* Context sent as argument to all command reply functions */ typedef struct { SilcServer server; SilcSocketConnection sock; SilcCommandPayload payload; + SilcArgumentPayload args; /* If defined this executes the pending command. */ - void *context; - SilcCommandCb callback; + SilcServerCommandPendingCallbacks callbacks; + uint32 callbacks_count; + uint16 ident; } *SilcServerCommandReplyContext; /* Macros */ @@ -49,29 +60,20 @@ typedef struct { /* Macro used to declare command reply functions */ #define SILC_SERVER_CMD_REPLY_FUNC(func) \ -void silc_server_command_reply_##func(void *context) - -/* Macro used to execute command replies */ -#define SILC_SERVER_COMMAND_REPLY_EXEC(ctx) \ -do { \ - SilcServerCommandReply *cmd; \ - \ - for (cmd = silc_command_reply_list; cmd->cb; cmd++) \ - if (cmd->cmd == silc_command_get(ctx->payload)) { \ - cmd->cb(ctx); \ - break; \ - } \ - \ - if (cmd == NULL) { \ - silc_free(ctx); \ - return; \ - } \ -} while(0) +void silc_server_command_reply_##func(void *context, void *context2) /* Prototypes */ +void silc_server_command_reply_free(SilcServerCommandReplyContext cmd); void silc_server_command_reply_process(SilcServer server, SilcSocketConnection sock, SilcBuffer buffer); +SILC_SERVER_CMD_REPLY_FUNC(whois); +SILC_SERVER_CMD_REPLY_FUNC(whowas); +SILC_SERVER_CMD_REPLY_FUNC(identify); +SILC_SERVER_CMD_REPLY_FUNC(info); +SILC_SERVER_CMD_REPLY_FUNC(motd); SILC_SERVER_CMD_REPLY_FUNC(join); +SILC_SERVER_CMD_REPLY_FUNC(users); +SILC_SERVER_CMD_REPLY_FUNC(getkey); #endif diff --git a/apps/silcd/idlist.c b/apps/silcd/idlist.c index 5f04a26f..4a72bcb9 100644 --- a/apps/silcd/idlist.c +++ b/apps/silcd/idlist.c @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -17,384 +17,765 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "serverincludes.h" #include "idlist.h" -/* Adds a new server to the list. The pointer sent as argument is allocated - and returned. */ +/****************************************************************************** + + Common functions + +******************************************************************************/ + +/* This function is used to add keys and stuff to common ID entry data + structure. */ -void silc_idlist_add_server(SilcServerList **list, - char *server_name, int server_type, - SilcServerID *id, SilcServerList *router, - SilcCipher send_key, SilcCipher receive_key, - SilcPKCS public_key, SilcHmac hmac, - SilcServerList **new_idlist) +void silc_idlist_add_data(void *entry, SilcIDListData idata) { - SilcServerList *last, *idlist; + SilcIDListData data = (SilcIDListData)entry; + data->send_key = idata->send_key; + data->receive_key = idata->receive_key; + data->hmac_send = idata->hmac_send; + data->hmac_receive = idata->hmac_receive; + data->psn_send = idata->psn_send; + data->psn_receive = idata->psn_receive; + data->hash = idata->hash; + data->public_key = idata->public_key; + data->rekey = idata->rekey; + data->last_receive = idata->last_receive; + data->last_sent = idata->last_sent; + data->status = idata->status; + + data->created = time(0); /* Update creation time */ +} - SILC_LOG_DEBUG(("Adding new server to id list")); +/* Free's all data in the common ID entry data structure. */ - idlist = silc_calloc(1, sizeof(*idlist)); - if (idlist == NULL) { - SILC_LOG_ERROR(("Could not allocate new server list object")); - *new_idlist = NULL; - return; +void silc_idlist_del_data(void *entry) +{ + SilcIDListData idata = (SilcIDListData)entry; + if (idata->send_key) + silc_cipher_free(idata->send_key); + if (idata->receive_key) + silc_cipher_free(idata->receive_key); + if (idata->rekey) { + if (idata->rekey->send_enc_key) { + memset(idata->rekey->send_enc_key, 0, idata->rekey->enc_key_len); + silc_free(idata->rekey->send_enc_key); + } + silc_free(idata->rekey); } + if (idata->hmac_send) + silc_hmac_free(idata->hmac_send); + if (idata->hmac_receive) + silc_hmac_free(idata->hmac_receive); + if (idata->public_key) + silc_pkcs_public_key_free(idata->public_key); +} - /* Set the pointers */ - idlist->server_name = server_name; - idlist->server_type = server_type; - idlist->id = id; - idlist->router = router; - idlist->send_key = send_key; - idlist->receive_key = receive_key; - idlist->public_key = public_key; - idlist->hmac = hmac; - idlist->next = idlist; - idlist->prev = idlist; - - /* First on the list? */ - if (!*list) { - *list = idlist; - *new_idlist = idlist; - return; - } +/* Purges ID cache */ + +SILC_TASK_CALLBACK_GLOBAL(silc_idlist_purge) +{ + SilcIDListPurge i = (SilcIDListPurge)context; - /* Add it to the list */ - last = (*list)->prev; - last->next = idlist; - (*list)->prev = idlist; - idlist->next = (*list); - idlist->prev = last; + SILC_LOG_DEBUG(("Start")); - if (new_idlist) - *new_idlist = idlist; + silc_idcache_purge(i->cache); + silc_schedule_task_add(i->schedule, 0, + silc_idlist_purge, + (void *)i, 600, 0, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); } -/* Adds a new client to the client list. This is called when new client - connection is accepted to the server. This adds all the relevant data - about the client and session with it to the list. This list is - referenced for example when sending message to the client. */ - -void silc_idlist_add_client(SilcClientList **list, char *nickname, - char *username, char *userinfo, - SilcClientID *id, SilcServerList *router, - SilcCipher send_key, SilcCipher receive_key, - SilcPKCS public_key, SilcHmac hmac, - SilcClientList **new_idlist) +/****************************************************************************** + + Server entry functions + +******************************************************************************/ + +/* Add new server entry. This adds the new server entry to ID cache and + returns the allocated entry object or NULL on error. This is called + when new server connects to us. We also add ourselves to cache with + this function. */ + +SilcServerEntry +silc_idlist_add_server(SilcIDList id_list, + char *server_name, int server_type, + SilcServerID *id, SilcServerEntry router, + void *connection) { - SilcClientList *last, *idlist; + SilcServerEntry server; - SILC_LOG_DEBUG(("Adding new client to id list")); + SILC_LOG_DEBUG(("Adding new server entry")); - idlist = silc_calloc(1, sizeof(*idlist)); - if (idlist == NULL) { - SILC_LOG_ERROR(("Could not allocate new client list object")); - return; - } + server = silc_calloc(1, sizeof(*server)); + server->server_name = server_name; + server->server_type = server_type; + server->id = id; + server->router = router; + server->connection = connection; - /* Set the pointers */ - idlist->nickname = nickname; - idlist->username = username; - idlist->userinfo = userinfo; - idlist->id = id; - idlist->router = router; - idlist->send_key = send_key; - idlist->receive_key = receive_key; - idlist->public_key = public_key; - idlist->hmac = hmac; - idlist->next = idlist; - idlist->prev = idlist; - - /* First on the list? */ - if (!(*list)) { - *list = idlist; - if (new_idlist) - *new_idlist = idlist; - return; + if (!silc_idcache_add(id_list->servers, server->server_name, + (void *)server->id, (void *)server, FALSE)) { + silc_free(server); + return NULL; } - /* Add it to the list */ - last = (*list)->prev; - last->next = idlist; - (*list)->prev = idlist; - idlist->next = *list; - idlist->prev = last; - - if (new_idlist) - *new_idlist = idlist; + return server; } -/* Free client entry. This free's everything. */ +/* Finds server by Server ID */ -void silc_idlist_del_client(SilcClientList **list, SilcClientList *entry) +SilcServerEntry +silc_idlist_find_server_by_id(SilcIDList id_list, SilcServerID *id, + bool registered, SilcIDCacheEntry *ret_entry) { - if (entry) { - if (entry->nickname) - silc_free(entry->nickname); - if (entry->username) - silc_free(entry->username); - if (entry->userinfo) - silc_free(entry->userinfo); - if (entry->id) - silc_free(entry->id); - if (entry->send_key) - silc_cipher_free(entry->send_key); - if (entry->receive_key) - silc_cipher_free(entry->receive_key); - if (entry->public_key) - silc_pkcs_free(entry->public_key); - if (entry->hmac) - silc_hmac_free(entry->hmac); - if (entry->hmac_key) { - memset(entry->hmac_key, 0, entry->hmac_key_len); - silc_free(entry->hmac_key); - } + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server; - /* Last one in list? */ - if (*list == entry && entry->next == entry) { - *list = NULL; - silc_free(entry); - return; - } + if (!id) + return NULL; - /* At the start of list? */ - if (*list == entry && entry->next != entry) { - *list = entry->next; - entry->next->prev = entry->prev; - entry->prev->next = *list; - silc_free(entry); - return; - } + SILC_LOG_DEBUG(("Server ID (%s)", + silc_id_render(id, SILC_ID_SERVER))); - /* Remove from list */ - entry->prev->next = entry->next; - entry->next->prev = entry->prev; - silc_free(entry); - return; - } + if (!silc_idcache_find_by_id_one(id_list->servers, (void *)id, + &id_cache)) + return NULL; + + server = (SilcServerEntry)id_cache->context; + + if (ret_entry) + *ret_entry = id_cache; + + if (server && registered && + !(server->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; + + SILC_LOG_DEBUG(("Found")); + + return server; } -SilcClientList * -silc_idlist_find_client_by_nickname(SilcClientList *list, - char *nickname, - char *server) +/* Find server by name */ + +SilcServerEntry +silc_idlist_find_server_by_name(SilcIDList id_list, char *name, + bool registered, SilcIDCacheEntry *ret_entry) { - SilcClientList *first, *entry; + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server; + + SILC_LOG_DEBUG(("Server by name `%s'", name)); + + if (!silc_idcache_find_by_name_one(id_list->servers, name, &id_cache)) + return NULL; - SILC_LOG_DEBUG(("Finding client by nickname")); + server = (SilcServerEntry)id_cache->context; + + if (ret_entry) + *ret_entry = id_cache; + + if (server && registered && + !(server->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; + + SILC_LOG_DEBUG(("Found")); + + return server; +} - if (!list) +/* Find server by connection parameters, hostname and port */ + +SilcServerEntry +silc_idlist_find_server_by_conn(SilcIDList id_list, char *hostname, + int port, bool registered, + SilcIDCacheEntry *ret_entry) +{ + SilcIDCacheList list = NULL; + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server = NULL; + SilcSocketConnection sock; + + SILC_LOG_DEBUG(("Server by hostname %s and port %d", hostname, port)); + + if (!silc_idcache_get_all(id_list->servers, &list)) return NULL; - first = entry = list; - if (!strcmp(entry->nickname, nickname)) { - SILC_LOG_DEBUG(("Found")); - return entry; + if (!silc_idcache_list_first(list, &id_cache)) { + silc_idcache_list_free(list); + return NULL; } - entry = entry->next; - while(entry != first) { - if (!strcmp(entry->nickname, nickname)) { - SILC_LOG_DEBUG(("Found")); - return entry; - } + while (id_cache) { + server = (SilcServerEntry)id_cache->context; + sock = (SilcSocketConnection)server->connection; + + if (sock && ((sock->hostname && !strcasecmp(sock->hostname, hostname)) || + (sock->ip && !strcasecmp(sock->ip, hostname))) + && sock->port == port) + break; + + id_cache = NULL; + server = NULL; - entry = entry->next; + if (!silc_idcache_list_next(list, &id_cache)) + break; } + + silc_idcache_list_free(list); - return NULL; + if (ret_entry) + *ret_entry = id_cache; + + if (server && registered && + !(server->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; + + SILC_LOG_DEBUG(("Found")); + + return server; } -SilcClientList * -silc_idlist_find_client_by_hash(SilcClientList *list, - char *nickname, SilcHash md5hash) +/* Replaces old Server ID with new one */ + +SilcServerEntry +silc_idlist_replace_server_id(SilcIDList id_list, SilcServerID *old_id, + SilcServerID *new_id) { - SilcClientList *first, *entry; - unsigned char hash[16]; + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server; + + if (!old_id || !new_id) + return NULL; - SILC_LOG_DEBUG(("Finding client by nickname hash")); + SILC_LOG_DEBUG(("Replacing Server ID")); - if (!list) + if (!silc_idcache_find_by_id_one(id_list->servers, (void *)old_id, + &id_cache)) return NULL; - /* Make hash of the nickname */ - silc_hash_make(md5hash, nickname, strlen(nickname), hash); + server = (SilcServerEntry)id_cache->context; - first = entry = list; - if (entry && !SILC_ID_COMPARE_HASH(entry->id, hash)) { - SILC_LOG_DEBUG(("Found")); - return entry; - } - entry = entry->next; + /* Remove the old entry and add a new one */ - while(entry != first) { - if (entry && !SILC_ID_COMPARE_HASH(entry->id, hash)) { - SILC_LOG_DEBUG(("Found")); - return entry; - } + silc_idcache_del_by_id(id_list->servers, (void *)server->id); - entry = entry->next; - } + silc_free(server->id); + server->id = new_id; + + silc_idcache_add(id_list->servers, server->server_name, server->id, + server, FALSE); - return NULL; + SILC_LOG_DEBUG(("Found")); + + return server; } -SilcClientList * -silc_idlist_find_client_by_id(SilcClientList *list, SilcClientID *id) +/* Removes and free's server entry from ID list */ + +int silc_idlist_del_server(SilcIDList id_list, SilcServerEntry entry) { - SilcClientList *first, *entry; + SILC_LOG_DEBUG(("Start")); - SILC_LOG_DEBUG(("Finding client by Client ID")); + if (entry) { + /* Remove from cache */ + if (entry->id) + if (!silc_idcache_del_by_id(id_list->servers, (void *)entry->id)) + return FALSE; - if (!list) - return NULL; + /* Free data */ + silc_free(entry->server_name); + silc_free(entry->id); - first = entry = list; - if (entry && !SILC_ID_CLIENT_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; + memset(entry, 'F', sizeof(*entry)); + silc_free(entry); + return TRUE; } - entry = entry->next; - while(entry != first) { - if (entry && !SILC_ID_CLIENT_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; - } + return FALSE; +} + +/****************************************************************************** + + Client entry functions + +******************************************************************************/ + +/* Add new client entry. This adds the client entry to ID cache system + and returns the allocated client entry or NULL on error. This is + called when new client connection is accepted to the server. If The + `router' is provided then the all server routines assume that the client + is not directly connected local client but it has router set and is + remote. If this is the case then `connection' must be NULL. If, on the + other hand, the `connection' is provided then the client is assumed + to be directly connected local client and `router' must be NULL. */ - entry = entry->next; +SilcClientEntry +silc_idlist_add_client(SilcIDList id_list, char *nickname, char *username, + char *userinfo, SilcClientID *id, + SilcServerEntry router, void *connection) +{ + SilcClientEntry client; + + SILC_LOG_DEBUG(("Adding new client entry")); + + client = silc_calloc(1, sizeof(*client)); + client->nickname = nickname; + client->username = username; + client->userinfo = userinfo; + client->id = id; + client->router = router; + client->connection = connection; + client->channels = silc_hash_table_alloc(3, silc_hash_ptr, NULL, + NULL, NULL, NULL, NULL, TRUE); + + if (!silc_idcache_add(id_list->clients, nickname, (void *)client->id, + (void *)client, FALSE)) { + silc_hash_table_free(client->channels); + silc_free(client); + return NULL; } - return NULL; + return client; } -/* Adds new channel to the list. */ +/* Free client entry. This free's everything and removes the entry + from ID cache. Call silc_idlist_del_data before calling this one. */ -void silc_idlist_add_channel(SilcChannelList **list, - char *channel_name, int mode, - SilcChannelID *id, SilcServerList *router, - SilcCipher channel_key, - SilcChannelList **new_idlist) +int silc_idlist_del_client(SilcIDList id_list, SilcClientEntry entry) { - SilcChannelList *last, *idlist; + SILC_LOG_DEBUG(("Start")); + + if (entry) { + /* Remove from cache */ + if (entry->id) + if (!silc_idcache_del_by_context(id_list->clients, entry)) + return FALSE; - SILC_LOG_DEBUG(("Adding new channel to id list")); + /* Free data */ + silc_free(entry->nickname); + silc_free(entry->username); + silc_free(entry->userinfo); + silc_free(entry->id); - idlist = silc_calloc(1, sizeof(*idlist)); - if (idlist == NULL) { - SILC_LOG_ERROR(("Could not allocate new channel list object")); - return; - } + memset(entry, 'F', sizeof(*entry)); + silc_free(entry); - /* Set the pointers */ - idlist->channel_name = channel_name; - idlist->mode = mode; - idlist->id = id; - idlist->router = router; - idlist->channel_key = channel_key; - idlist->next = idlist; - idlist->prev = idlist; - - /* First on the list? */ - if (!*list) { - *list = idlist; - if (new_idlist) - *new_idlist = idlist; - return; + return TRUE; } - /* Add it to the list */ - last = (*list)->prev; - last->next = idlist; - (*list)->prev = idlist; - idlist->next = (*list); - idlist->prev = last; + return FALSE; +} + +/* Returns all clients matching requested nickname. Number of clients is + returned to `clients_count'. Caller must free the returned table. */ + +int silc_idlist_get_clients_by_nickname(SilcIDList id_list, char *nickname, + char *server, + SilcClientEntry **clients, + uint32 *clients_count) +{ + SilcIDCacheList list = NULL; + SilcIDCacheEntry id_cache = NULL; + + SILC_LOG_DEBUG(("Start")); + + if (!silc_idcache_find_by_name(id_list->clients, nickname, &list)) + return FALSE; + + *clients = silc_realloc(*clients, + (silc_idcache_list_count(list) + *clients_count) * + sizeof(**clients)); + + silc_idcache_list_first(list, &id_cache); + (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context; + + while (silc_idcache_list_next(list, &id_cache)) + (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context; + + silc_idcache_list_free(list); + + SILC_LOG_DEBUG(("Found total %d clients", *clients_count)); + + return TRUE; +} + +/* Returns all clients matching requested nickname hash. Number of clients + is returned to `clients_count'. Caller must free the returned table. */ + +int silc_idlist_get_clients_by_hash(SilcIDList id_list, char *nickname, + SilcHash md5hash, + SilcClientEntry **clients, + uint32 *clients_count) +{ + SilcIDCacheList list = NULL; + SilcIDCacheEntry id_cache = NULL; + unsigned char hash[32]; + SilcClientID client_id; + + SILC_LOG_DEBUG(("Start")); - if (new_idlist) - *new_idlist = idlist; + silc_hash_make(md5hash, nickname, strlen(nickname), hash); + + /* As the Client ID is hashed in the ID cache by hashing only the hash + from the Client ID, we can do a lookup with only the hash not the + other parts of the ID and get all the clients with that hash, ie. + with that nickname, as the hash is from the nickname. */ + memset(&client_id, 0, sizeof(client_id)); + memcpy(&client_id.hash, hash, sizeof(client_id.hash)); + if (!silc_idcache_find_by_id(id_list->clients, &client_id, &list)) + return FALSE; + + *clients = silc_realloc(*clients, + (silc_idcache_list_count(list) + *clients_count) * + sizeof(**clients)); + + silc_idcache_list_first(list, &id_cache); + (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context; + + while (silc_idcache_list_next(list, &id_cache)) + (*clients)[(*clients_count)++] = (SilcClientEntry)id_cache->context; + + silc_idcache_list_free(list); + + SILC_LOG_DEBUG(("Found total %d clients", *clients_count)); + + return TRUE; } -SilcChannelList * -silc_idlist_find_channel_by_id(SilcChannelList *list, SilcChannelID *id) +/* Finds client by Client ID */ + +SilcClientEntry +silc_idlist_find_client_by_id(SilcIDList id_list, SilcClientID *id, + bool registered, SilcIDCacheEntry *ret_entry) { - SilcChannelList *first, *entry; + SilcIDCacheEntry id_cache = NULL; + SilcClientEntry client; + + if (!id) + return NULL; + + SILC_LOG_DEBUG(("Client ID (%s)", + silc_id_render(id, SILC_ID_CLIENT))); + + /* Do extended search since the normal ID comparison function for + Client ID's compares only the hash from the Client ID and not the + entire ID. The silc_hash_client_id_compare compares the entire + Client ID as we want to find one specific Client ID. */ + if (!silc_idcache_find_by_id_one_ext(id_list->clients, (void *)id, + NULL, NULL, + silc_hash_client_id_compare, NULL, + &id_cache)) + return NULL; + + client = (SilcClientEntry)id_cache->context; + + if (ret_entry) + *ret_entry = id_cache; + + if (client && registered && + !(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) + return NULL; + + SILC_LOG_DEBUG(("Found")); + + return client; +} + +/* Replaces old Client ID with new one */ + +SilcClientEntry +silc_idlist_replace_client_id(SilcIDList id_list, SilcClientID *old_id, + SilcClientID *new_id) +{ + SilcIDCacheEntry id_cache = NULL; + SilcClientEntry client; + + if (!old_id || !new_id) + return NULL; - SILC_LOG_DEBUG(("Finding channel by Channel ID")); + SILC_LOG_DEBUG(("Replacing Client ID")); - if (!list) + /* Do extended search since the normal ID comparison function for + Client ID's compares only the hash from the Client ID and not the + entire ID. The silc_hash_client_id_compare compares the entire + Client ID as we want to find one specific Client ID. */ + if (!silc_idcache_find_by_id_one_ext(id_list->clients, (void *)old_id, + NULL, NULL, + silc_hash_client_id_compare, NULL, + &id_cache)) return NULL; - first = entry = list; - if (entry && !SILC_ID_CHANNEL_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; + client = (SilcClientEntry)id_cache->context; + + /* Remove the old entry and add a new one */ + + silc_idcache_del_by_context(id_list->clients, client); + + silc_free(client->id); + client->id = new_id; + + silc_idcache_add(id_list->clients, NULL, client->id, client, FALSE); + + SILC_LOG_DEBUG(("Replaced")); + + return client; +} + +/* Client cache entry destructor that is called when the cache is purged. */ + +void silc_idlist_client_destructor(SilcIDCache cache, + SilcIDCacheEntry entry) +{ + SilcClientEntry client; + + SILC_LOG_DEBUG(("Start")); + + client = (SilcClientEntry)entry->context; + if (client) { + if (client->nickname) + silc_free(client->nickname); + if (client->username) + silc_free(client->username); + if (client->userinfo) + silc_free(client->userinfo); + if (client->id) + silc_free(client->id); + + memset(client, 'F', sizeof(*client)); + silc_free(client); } - entry = entry->next; +} + +/****************************************************************************** - while(entry != first) { - if (entry && !SILC_ID_CHANNEL_COMPARE(entry->id, id)) { - SILC_LOG_DEBUG(("Found")); - return entry; + Channel entry functions + +******************************************************************************/ + +/* Add new channel entry. This add the new channel entry to the ID cache + system and returns the allocated entry or NULL on error. */ + +SilcChannelEntry +silc_idlist_add_channel(SilcIDList id_list, char *channel_name, int mode, + SilcChannelID *id, SilcServerEntry router, + SilcCipher channel_key, SilcHmac hmac) +{ + SilcChannelEntry channel; + + SILC_LOG_DEBUG(("Adding new channel entry")); + + channel = silc_calloc(1, sizeof(*channel)); + channel->channel_name = channel_name; + channel->mode = mode; + channel->id = id; + channel->router = router; + channel->channel_key = channel_key; + channel->hmac = hmac; + channel->created = time(0); + if (!channel->hmac) + if (!silc_hmac_alloc(SILC_DEFAULT_HMAC, NULL, &channel->hmac)) { + silc_free(channel); + return NULL; } - entry = entry->next; + channel->user_list = silc_hash_table_alloc(3, silc_hash_ptr, NULL, NULL, + NULL, NULL, NULL, TRUE); + + if (!silc_idcache_add(id_list->channels, channel->channel_name, + (void *)channel->id, (void *)channel, FALSE)) { + silc_hmac_free(channel->hmac); + silc_hash_table_free(channel->user_list); + silc_free(channel); + return NULL; } - return NULL; + return channel; +} + +/* Foreach callbcak to free all users from the channel when deleting a + channel entry. */ + +static void silc_idlist_del_channel_foreach(void *key, void *context, + void *user_context) +{ + SilcChannelClientEntry chl = (SilcChannelClientEntry)context; + + /* Remove the context from the client's channel hash table as that + table and channel's user_list hash table share this same context. */ + silc_hash_table_del(chl->client->channels, chl->channel); + silc_free(chl); } /* Free channel entry. This free's everything. */ -void silc_idlist_del_channel(SilcChannelList **list, SilcChannelList *entry) +int silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry) { + SILC_LOG_DEBUG(("Start")); + if (entry) { - if (entry->channel_name) - silc_free(entry->channel_name); + /* Remove from cache */ if (entry->id) - silc_free(entry->id); - if (entry->topic) - silc_free(entry->topic); + if (!silc_idcache_del_by_id(id_list->channels, (void *)entry->id)) + return FALSE; + + /* Free data */ + silc_free(entry->channel_name); + silc_free(entry->id); + silc_free(entry->topic); if (entry->channel_key) silc_cipher_free(entry->channel_key); if (entry->key) { - memset(entry->key, 0, entry->key_len); + memset(entry->key, 0, entry->key_len / 8); silc_free(entry->key); } - memset(entry->iv, 0, sizeof(entry->iv)); + silc_free(entry->cipher); + silc_free(entry->hmac_name); + silc_free(entry->rekey); + + /* Free all client entrys from the users list. The silc_hash_table_free + will free all the entries so they are not freed at the foreach + callback. */ + silc_hash_table_foreach(entry->user_list, silc_idlist_del_channel_foreach, + NULL); + silc_hash_table_free(entry->user_list); + + memset(entry, 'F', sizeof(*entry)); + silc_free(entry); + return TRUE; + } - if (entry->user_list_count) - silc_free(entry->user_list); + return FALSE; +} - /* Last one in list? */ - if (*list == entry && entry->next == entry) { - *list = NULL; - silc_free(entry); - return; - } +/* Finds channel by channel name. Channel names are unique and they + are not case-sensitive. */ - /* At the start of list? */ - if (*list == entry && entry->next != entry) { - *list = entry->next; - entry->next->prev = entry->prev; - entry->prev->next = *list; - silc_free(entry); - return; - } +SilcChannelEntry +silc_idlist_find_channel_by_name(SilcIDList id_list, char *name, + SilcIDCacheEntry *ret_entry) +{ + SilcIDCacheEntry id_cache = NULL; - /* Remove from list */ - entry->prev->next = entry->next; - entry->next->prev = entry->prev; - silc_free(entry); - return; + SILC_LOG_DEBUG(("Channel by name")); + + if (!silc_idcache_find_by_name_one(id_list->channels, name, &id_cache)) + return NULL; + + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); + + return id_cache->context; +} + +/* Finds channel by Channel ID. */ + +SilcChannelEntry +silc_idlist_find_channel_by_id(SilcIDList id_list, SilcChannelID *id, + SilcIDCacheEntry *ret_entry) +{ + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry channel; + + if (!id) + return NULL; + + SILC_LOG_DEBUG(("Channel ID (%s)", + silc_id_render(id, SILC_ID_CHANNEL))); + + if (!silc_idcache_find_by_id_one(id_list->channels, (void *)id, &id_cache)) + return NULL; + + channel = (SilcChannelEntry)id_cache->context; + + if (ret_entry) + *ret_entry = id_cache; + + SILC_LOG_DEBUG(("Found")); + + return channel; +} + +/* Replaces old Channel ID with new one. This is done when router forces + normal server to change Channel ID. */ + +SilcChannelEntry +silc_idlist_replace_channel_id(SilcIDList id_list, SilcChannelID *old_id, + SilcChannelID *new_id) +{ + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry channel; + + if (!old_id || !new_id) + return NULL; + + SILC_LOG_DEBUG(("Replacing Channel ID")); + + if (!silc_idcache_find_by_id_one(id_list->channels, (void *)old_id, + &id_cache)) + return NULL; + + channel = (SilcChannelEntry)id_cache->context; + + /* Remove the old entry and add a new one */ + + silc_idcache_del_by_id(id_list->channels, (void *)channel->id); + + silc_free(channel->id); + channel->id = new_id; + + silc_idcache_add(id_list->channels, channel->channel_name, channel->id, + channel, FALSE); + + SILC_LOG_DEBUG(("Replaced")); + + return channel; +} + +/* Returns channels from the ID list. If the `channel_id' is NULL then + all channels are returned. */ + +SilcChannelEntry * +silc_idlist_get_channels(SilcIDList id_list, SilcChannelID *channel_id, + uint32 *channels_count) +{ + SilcIDCacheList list = NULL; + SilcIDCacheEntry id_cache = NULL; + SilcChannelEntry *channels = NULL; + int i = 0; + + SILC_LOG_DEBUG(("Start")); + + if (!channel_id) { + if (!silc_idcache_get_all(id_list->channels, &list)) + return NULL; + + channels = silc_calloc(silc_idcache_list_count(list), sizeof(*channels)); + + i = 0; + silc_idcache_list_first(list, &id_cache); + channels[i++] = (SilcChannelEntry)id_cache->context; + + while (silc_idcache_list_next(list, &id_cache)) + channels[i++] = (SilcChannelEntry)id_cache->context; + + silc_idcache_list_free(list); + } else { + if (!silc_idcache_find_by_id_one(id_list->channels, channel_id, &id_cache)) + return NULL; + + i = 1; + channels = silc_calloc(1, sizeof(*channels)); + channels[0] = (SilcChannelEntry)id_cache->context; } + + if (channels_count) + *channels_count = i; + + return channels; } diff --git a/apps/silcd/idlist.h b/apps/silcd/idlist.h index b18b90dd..0436d9a4 100644 --- a/apps/silcd/idlist.h +++ b/apps/silcd/idlist.h @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -22,19 +22,98 @@ #define IDLIST_H /* Forward declarations */ -typedef struct SilcServerListStruct SilcServerList; -typedef struct SilcClientListStruct SilcClientList; -typedef struct SilcChannelListStruct SilcChannelList; +typedef struct SilcServerEntryStruct *SilcServerEntry; +typedef struct SilcClientEntryStruct *SilcClientEntry; +typedef struct SilcChannelEntryStruct *SilcChannelEntry; + +/* Context for holding cache information to periodically purge + the cache. */ +typedef struct { + SilcIDCache cache; + SilcSchedule schedule; +} *SilcIDListPurge; + +/* Channel key re-key context. */ +typedef struct { + void *context; + SilcChannelEntry channel; + uint32 key_len; + SilcTask task; +} *SilcServerChannelRekey; + +/* Generic rekey context for connections */ +typedef struct { + /* Current sending encryption key, provided for re-key. The `pfs' + is TRUE if the Perfect Forward Secrecy is performed in re-key. */ + unsigned char *send_enc_key; + uint32 enc_key_len; + int ske_group; + bool pfs; + uint32 timeout; + void *context; +} *SilcServerRekey; + +/* ID List Entry status type and all the types. */ +typedef uint8 SilcIDListStatus; +#define SILC_IDLIST_STATUS_NONE 0x00 /* No status */ +#define SILC_IDLIST_STATUS_REGISTERED 0x01 /* Entry is registered */ +#define SILC_IDLIST_STATUS_RESOLVED 0x02 /* Entry info is resolved */ +#define SILC_IDLIST_STATUS_RESOLVING 0x04 /* Entry is being resolved + with WHOIS or IDENTIFY */ +#define SILC_IDLIST_STATUS_DISABLED 0x08 /* Entry is disabled */ + +/* + Generic ID list data structure. + + This structure is included in all ID list entries and it includes data + pointers that are common to all ID entries. This structure is always + defined to the first field in the ID entries and is used to explicitly + type cast to this type without first explicitly casting to correct ID + entry type. Hence, the ID list entry is type casted to this type to + get this data from the ID entry (which is usually opaque pointer). + + Note that some of the fields may be NULL. + +*/ +typedef struct { + /* Send and receive symmetric keys */ + SilcCipher send_key; + SilcCipher receive_key; + + /* HMAC */ + SilcHmac hmac_send; + SilcHmac hmac_receive; + + /* Packet sequence numbers */ + uint32 psn_send; + uint32 psn_receive; + + /* Hash selected in the SKE protocol, NULL if not needed at all */ + SilcHash hash; + + /* Public key */ + SilcPublicKey public_key; + + /* Re-key context */ + SilcServerRekey rekey; + + long last_receive; /* Time last received data */ + long last_sent; /* Time last sent data */ + + unsigned long created; /* Time when entry was created */ + + SilcIDListStatus status; /* Status mask of the entry */ +} *SilcIDListData, SilcIDListDataStruct; /* - SILC Server list object. + SILC Server entry object. - This list holds information about servers in SILC network. However, - contents of this list is highly dependent of what kind of server we are - (normal server or router server) and whether the list is used as a local - list or a global list. These factors dictates the contents of this list. + This entry holds information about servers in SILC network. However, + contents of this entry is highly dependent of what kind of server we are + (normal server or router server) and whether the entry is used as a local + list or a global list. These factors dictates the contents of this entry. - This list is defined as follows: + This entry is defined as follows: Server type List type Contents ======================================================================= @@ -45,12 +124,16 @@ typedef struct SilcChannelListStruct SilcChannelList; Following short description of the fields: + SilcIDListDataStruct data + + Generic data structure to hold data common to all ID entries. + char *server_name Logical name of the server. There is no limit of the length of the server name. This is usually the same name as defined in DNS. - int server_type + uint8 server_type Type of the server. SILC_SERVER or SILC_ROUTER are the possible choices for this. @@ -61,16 +144,23 @@ typedef struct SilcChannelListStruct SilcChannelList; the server SILC will ever need. These are also the informations that is broadcasted between servers and routers in the SILC network. - struct SilcServerListStruct *router + char *server_info + char *motd + + Server info (from INFO command) saved temporarily and motd (from + MOTD command) saved temporarily. + + SilcServerEntry router This is a pointer back to the server list. This is the router server where this server is connected to. If this is the router itself and it doesn't have a route this is NULL. SilcCipher send_key - SilcCipher receive_key + Data sending and receiving keys. + void *connection A pointer, usually, to the socket list for fast referencing to @@ -79,37 +169,59 @@ typedef struct SilcChannelListStruct SilcChannelList; list. */ -struct SilcServerListStruct { +struct SilcServerEntryStruct { + /* Generic data structure. DO NOT add anything before this! */ + SilcIDListDataStruct data; + char *server_name; - int server_type; + uint8 server_type; SilcServerID *id; + char *server_info; + char *motd; /* Pointer to the router */ - struct SilcServerListStruct *router; - - /* Keys */ - SilcCipher send_key; - SilcCipher receive_key; - SilcPKCS public_key; - SilcHmac hmac; - unsigned char *hmac_key; - unsigned int hmac_key_len; + SilcServerEntry router; /* Connection data */ void *connection; - - struct SilcServerListStruct *next; - struct SilcServerListStruct *prev; }; /* - SILC Client list object. + SILC Channel Client entry structure. + + This entry used only by the SilcChannelEntry object and it holds + information about current clients (ie. users) on channel. Following + short description of the fields: + + SilcClientEntry client + + Pointer to the client list. This is the client currently on channel. + + uint32 mode + + Client's current mode on the channel. - This list holds information about connected clients ie. users in the SILC - network. The contents of this list is depended on whether we are normal + SilcChannelEntry channel + + Back pointer back to channel. As this structure is also used by + SilcClientEntry we have this here for fast access to the channel when + used by SilcClientEntry. + +*/ +typedef struct SilcChannelClientEntryStruct { + SilcClientEntry client; + uint32 mode; + SilcChannelEntry channel; +} *SilcChannelClientEntry; + +/* + SILC Client entry object. + + This entry holds information about connected clients ie. users in the SILC + network. The contents of this entrt is depended on whether we are normal server or router server and whether the list is a local or global list. - This list is defined as follows: + This entry is defined as follows: Server type List type Contents ======================================================================= @@ -120,10 +232,21 @@ struct SilcServerListStruct { Following short description of the fields: + SilcIDListDataStruct data + + Generic data structure to hold data common to all ID entries. + + unsigned char *nickname + + The nickname of the client. + + char *servername + + The name of the server where the client is from. MAy be NULL. + char username - Client's (meaning user's) real name. This is defined in following - manner: + Client's usename. This is defined in the following manner: Server type List type Contents ==================================================== @@ -159,31 +282,34 @@ struct SilcServerListStruct { nickname. Nickname is not relevant information that would need to be saved as plain. - int mode + uint32 mode Client's mode. Client maybe for example server operator or router operator (SILC operator). - SilcServerList *router + long last_command - This is a pointer to the server list. This is the router server whose - cell this client is coming from. This is used to route messages to - this client. + Time of last time client executed command. We are strict and will + not allow any command to be exeucted more than once in about + 2 seconds. This is result of normal time(). - SilcCipher session_key + uint8 fast_command - The actual session key established by key exchange protcol between - connecting parties. This is used for both encryption and decryption. + Counter to check command bursts. By default, up to 5 commands + are allowed before limiting the execution. See command flags + for more detail. - SilcPKCS public_key + SilcServerEntry router - Public key of the client. This maybe NULL. + This is a pointer to the server list. This is the router server whose + cell this client is coming from. This is used to route messages to + this client. - SilcHmac hmac - unsigned char *hmac_key - unsigned int hmac_key_len + SilcHashTable channels; - MAC key used to compute MAC's for packets. + All the channels this client has joined. The context saved in the + hash table shares memory with the channel entrys `user_list' hash + table. void *connection @@ -192,65 +318,51 @@ struct SilcServerListStruct { but as just said, this is usually pointer to the socket connection list. + uint16 resolve_cmd_ident + + Command identifier for the entry when the entry's data.status + is SILC_IDLIST_STATUS_RESOLVING. If this entry is asked to be + resolved when the status is set then the resolver may attach to + this command identifier and handle the process after the resolving + is over. + */ -struct SilcClientListStruct { - char *nickname; +struct SilcClientEntryStruct { + /* Generic data structure. DO NOT add anything before this! */ + SilcIDListDataStruct data; + + unsigned char *nickname; + char *servername; char *username; char *userinfo; SilcClientID *id; - int mode; + uint32 mode; + + long last_command; + uint8 fast_command; /* Pointer to the router */ - SilcServerList *router; + SilcServerEntry router; - /* Pointers to channels this client has joined */ - SilcChannelList **channel; - unsigned int channel_count; - - /* Keys */ - SilcCipher send_key; - SilcCipher receive_key; - SilcPKCS public_key; - SilcHmac hmac; - unsigned char *hmac_key; - unsigned int hmac_key_len; + /* All channels this client has joined */ + SilcHashTable channels; /* Connection data */ void *connection; - struct SilcClientListStruct *next; - struct SilcClientListStruct *prev; + /* data.status is RESOLVING and this includes the resolving command + reply identifier. */ + uint16 resolve_cmd_ident; }; /* - SILC Channel Client list structure. - - This list used only by the SilcChannelList object and it holds information - about current clients (ie. users) on channel. Following short description - of the fields: + SILC Channel entry object. - SilcClientList client - - Pointer to the client list. This is the client currently on channel. - - int mode - - Client's current mode on the channel. - -*/ -typedef struct SilcChannelClientListStruct { - SilcClientList *client; - int mode; -} SilcChannelClientList; - -/* - SILC Channel list object. - - This list holds information about channels in SILC network. The contents - of this list is depended on whether we are normal server or router server + This entry holds information about channels in SILC network. The contents + of this entry is depended on whether we are normal server or router server and whether the list is a local or global list. - This list is defined as follows: + This entry is defined as follows: Server type List type Contents ======================================================================= @@ -265,16 +377,17 @@ typedef struct SilcChannelClientListStruct { Logical name of the channel. - int mode + uint32 mode - Current mode of the channel. + Current mode of the channel. See lib/silccore/silcchannel.h for + all modes. SilcChannelID *id ID of the channel. This includes all the information SILC will ever need. - int global_users + bool global_users Boolean value to tell whether there are users outside this server on this channel. This is set to TRUE if router sends message to @@ -288,40 +401,97 @@ typedef struct SilcChannelClientListStruct { Current topic of the channel. - SilcServerList *router + char *cipher + + Default cipher of the channel. If this is NULL then server picks + the cipher to be used. This can be set at SILC_COMMAND_JOIN. + + char *hmac_name + + Default hmac of the channel. If this is NULL then server picks + the cipher to be used. This can be set at SILC_COMMAND_JOIN. + + SilcPublicKey founder_key + SilcAuthMethod founder_method + unsigned char *founder_passwd + uint32 founder_passwd_len + + If the SILC_CMODE_FOUNDER_AUTH has been set then these will include + the founder's public key, authentication method and the password + if the method is SILC_AUTH_PASSWORD. If it is SILC_AUTH_PUBLIC_KEY + then the `founder_passwd' is NULL. + + SilcHashTable user_list + + All users joined on this channel. Note that the context saved to + this entry shares memory with the client entrys `channels' hash + table. + + SilcServerEntry router This is a pointer to the server list. This is the router server whose cell this channel belongs to. This is used to route messages to this channel. - SilcCipher send_key + SilcCipher channel_key + The key of the channel (the cipher actually). - SilcCipher receive_key + unsigned char *key + uint32 key_len + + Raw key data of the channel key. + + unsigned char iv[SILC_CIPHER_MAX_IV_SIZE] + + Current initial vector. Initial vector is received always along + with the channel packet. By default this is filled with NULL. + + SilcHmac hmac; + + HMAC of the channel. + + SilcServerChannelRekey rekey + + Channel key re-key context. */ -struct SilcChannelListStruct { +struct SilcChannelEntryStruct { char *channel_name; - int mode; + uint32 mode; SilcChannelID *id; - int global_users; + bool global_users; char *topic; + char *cipher; + char *hmac_name; - /* List of users on channel */ - SilcChannelClientList *user_list; - unsigned int user_list_count; + SilcPublicKey founder_key; + SilcAuthMethod founder_method; + unsigned char *founder_passwd; + uint32 founder_passwd_len; + + uint32 user_limit; + unsigned char *passphrase; + char *invite_list; + char *ban_list; + + /* All users on this channel */ + SilcHashTable user_list; /* Pointer to the router */ - SilcServerList *router; + SilcServerEntry router; /* Channel keys */ SilcCipher channel_key; unsigned char *key; - unsigned int key_len; + uint32 key_len; unsigned char iv[SILC_CIPHER_MAX_IV_SIZE]; + SilcHmac hmac; + + SilcServerChannelRekey rekey; - struct SilcChannelListStruct *next; - struct SilcChannelListStruct *prev; + unsigned long created; + bool disabled; }; /* @@ -329,22 +499,22 @@ struct SilcChannelListStruct { As for remainder these lists are defined as follows: - List Server type List type Contents + Entry list (cache) Server type List type Contents ======================================================================= - servers server local list Server itself - servers server global list NULL - servers router local list All servers in cell - servers router global list All servers in SILC + servers server local list Server itself + servers server global list NULL + servers router local list All servers in cell + servers router global list All servers in SILC - clients server local list All clients in server - clients server global list NULL - clients router local list All clients in cell - clients router global list All clients in SILC + clients server local list All clients in server + clients server global list NULL + clients router local list All clients in cell + clients router global list All clients in SILC - channels server local list All channels in server - channels server global list NULL - channels router local list All channels in cell - channels router global list All channels in SILC + channels server local list All channels in server + channels server global list NULL + channels router local list All channels in cell + channels router global list All channels in SILC As seen on the list normal server never defines a global list. This is because of normal server don't know anything about anything global data, @@ -352,77 +522,96 @@ struct SilcChannelListStruct { other hand, always define local and global lists because routers really know all the relevant data in the SILC network. -*/ -typedef struct SilcIDListStruct { - SilcServerList *servers; - SilcClientList *clients; - SilcChannelList *channels; + This object is used as local and global list by the server/router. + Above table shows how this is defined on different conditions. - /* ID Caches. Caches are used to perform fast search on the ID's. */ - SilcIDCache *server_cache[96]; - unsigned int server_cache_count[96]; - SilcIDCache *client_cache[96]; - unsigned int client_cache_count[96]; - SilcIDCache *channel_cache[96]; - unsigned int channel_cache_count[96]; -} SilcIDListObject; + This object holds pointers to the ID cache system. Every ID cache entry + has a specific context pointer to allocated entry (server, client or + channel entry). -typedef SilcIDListObject *SilcIDList; +*/ +typedef struct SilcIDListStruct { + SilcIDCache servers; + SilcIDCache clients; + SilcIDCache channels; +} *SilcIDList; /* - Temporary ID List object. + ID Entry for Unknown connections. - This is used during authentication phases where we still don't - know what kind of connection remote connection is, hence, we - will use this structure instead until we know what type of - connection remote end is. + This is used during authentication phases where we still don't know + what kind of connection remote connection is, hence, we will use this + structure instead until we know what type of connection remote end is. - This is not in any list. This is always individually allocated - and used as such. + This is not in any list. This is always individually allocated and + used as such. */ typedef struct { - SilcCipher send_key; - SilcCipher receive_key; - SilcPKCS pkcs; - - SilcHmac hmac; - unsigned char *hmac_key; - unsigned int hmac_key_len; - - /* SilcComp comp */ -} SilcIDListUnknown; + /* Generic data structure. DO NOT add anything before this! */ + SilcIDListDataStruct data; +} *SilcUnknownEntry; /* Prototypes */ -void silc_idlist_add_server(SilcServerList **list, - char *server_name, int server_type, - SilcServerID *id, SilcServerList *router, - SilcCipher send_key, SilcCipher receive_key, - SilcPKCS public_key, SilcHmac hmac, - SilcServerList **new_idlist); -void silc_idlist_add_client(SilcClientList **list, char *nickname, - char *username, char *userinfo, - SilcClientID *id, SilcServerList *router, - SilcCipher send_key, SilcCipher receive_key, - SilcPKCS public_key, SilcHmac hmac, - SilcClientList **new_idlist); -void silc_idlist_del_client(SilcClientList **list, SilcClientList *entry); -SilcClientList * -silc_idlist_find_client_by_nickname(SilcClientList *list, - char *nickname, - char *server); -SilcClientList * -silc_idlist_find_client_by_hash(SilcClientList *list, - char *nickname, SilcHash hash); -SilcClientList * -silc_idlist_find_client_by_id(SilcClientList *list, SilcClientID *id); -void silc_idlist_add_channel(SilcChannelList **list, - char *channel_name, int mode, - SilcChannelID *id, SilcServerList *router, - SilcCipher channel_key, - SilcChannelList **new_idlist); -SilcChannelList * -silc_idlist_find_channel_by_id(SilcChannelList *list, SilcChannelID *id); -void silc_idlist_del_channel(SilcChannelList **list, SilcChannelList *entry); +void silc_idlist_add_data(void *entry, SilcIDListData idata); +void silc_idlist_del_data(void *entry); +SILC_TASK_CALLBACK_GLOBAL(silc_idlist_purge); +SilcServerEntry +silc_idlist_add_server(SilcIDList id_list, + char *server_name, int server_type, + SilcServerID *id, SilcServerEntry router, + void *connection); +SilcServerEntry +silc_idlist_find_server_by_id(SilcIDList id_list, SilcServerID *id, + bool registered, SilcIDCacheEntry *ret_entry); +SilcServerEntry +silc_idlist_find_server_by_name(SilcIDList id_list, char *name, + bool registered, SilcIDCacheEntry *ret_entry); +SilcServerEntry +silc_idlist_find_server_by_conn(SilcIDList id_list, char *hostname, + int port, bool registered, + SilcIDCacheEntry *ret_entry); +SilcServerEntry +silc_idlist_replace_server_id(SilcIDList id_list, SilcServerID *old_id, + SilcServerID *new_id); +int silc_idlist_del_server(SilcIDList id_list, SilcServerEntry entry); +SilcClientEntry +silc_idlist_add_client(SilcIDList id_list, char *nickname, char *username, + char *userinfo, SilcClientID *id, + SilcServerEntry router, void *connection); +int silc_idlist_del_client(SilcIDList id_list, SilcClientEntry entry); +int silc_idlist_get_clients_by_nickname(SilcIDList id_list, char *nickname, + char *server, + SilcClientEntry **clients, + uint32 *clients_count); +int silc_idlist_get_clients_by_hash(SilcIDList id_list, char *nickname, + SilcHash md5hash, + SilcClientEntry **clients, + uint32 *clients_count); +SilcClientEntry +silc_idlist_find_client_by_id(SilcIDList id_list, SilcClientID *id, + bool registered, SilcIDCacheEntry *ret_entry); +SilcClientEntry +silc_idlist_replace_client_id(SilcIDList id_list, SilcClientID *old_id, + SilcClientID *new_id); +void silc_idlist_client_destructor(SilcIDCache cache, + SilcIDCacheEntry entry); +SilcChannelEntry +silc_idlist_add_channel(SilcIDList id_list, char *channel_name, int mode, + SilcChannelID *id, SilcServerEntry router, + SilcCipher channel_key, SilcHmac hmac); +int silc_idlist_del_channel(SilcIDList id_list, SilcChannelEntry entry); +SilcChannelEntry +silc_idlist_find_channel_by_name(SilcIDList id_list, char *name, + SilcIDCacheEntry *ret_entry); +SilcChannelEntry +silc_idlist_find_channel_by_id(SilcIDList id_list, SilcChannelID *id, + SilcIDCacheEntry *ret_entry); +SilcChannelEntry +silc_idlist_replace_channel_id(SilcIDList id_list, SilcChannelID *old_id, + SilcChannelID *new_id); +SilcChannelEntry * +silc_idlist_get_channels(SilcIDList id_list, SilcChannelID *channel_id, + uint32 *channels_count); #endif diff --git a/apps/silcd/leevi.conf b/apps/silcd/leevi.conf deleted file mode 100644 index d94631f7..00000000 --- a/apps/silcd/leevi.conf +++ /dev/null @@ -1,47 +0,0 @@ -[Cipher] -twofish:/home/priikone/silc/lib/silcsim/modules/twofish.sim.so:16:16 -rc6:/home/priikone/silc/lib/silcsim/modules/rc6.sim.so:16:16 -mars:/home/priikone/silc/lib/silcsim/modules/mars.sim.so:16:16 -none:/home/priikone/silc/lib/silcsim/modules/none.sim.so:0:0 - -[HashFunction] -md5::64:16 -sha1::64:20 - -#[PKCS] -#rsa::1024 -#dss::1024 - -[ServerInfo] -leevi.kuo.fi.ssh.com:10.2.1.7:Kuopio, Finland:1333 - -[AdminInfo] -Mun huone:Mun servo:Pekka Riikonen:priikone@poseidon.pspt.fi - -[ListenPort] -10.2.1.7:10.2.1.7:1333 - -[Logging] -infologfile:silcd.log:10000 -#warninglogfile:/var/log/silcd_warning.log:10000 -errorlogfile:leevi_error.log:10000 -#fatallogfile:/var/log/silcd_error.log: - -[ConnectionClass] -1:100:100:100 -2:200:300:400 - -[ClientConnection] -10.2.1.199:passwd:priikone:333:1 -:::1333:1 - -[AdminConnection] -10.2.1.199:passwd:priikone:priikone:1 - -[ServerConnection] -10.2.1.7:passwd:priikone:1334:1:1 - -[RouterConnection] - -[DenyConnection] -[RedirectClient] diff --git a/apps/silcd/leevi2.conf b/apps/silcd/leevi2.conf deleted file mode 100644 index 4368321f..00000000 --- a/apps/silcd/leevi2.conf +++ /dev/null @@ -1,47 +0,0 @@ -[Cipher] -twofish:/home/priikone/silc/lib/silcsim/modules/twofish.sim.so:16:16 -rc6:/home/priikone/silc/lib/silcsim/modules/rc6.sim.so:16:16 -mars:/home/priikone/silc/lib/silcsim/modules/mars.sim.so:16:16 -none:/home/priikone/silc/lib/silcsim/modules/none.sim.so:0:0 - -[HashFunction] -md5::64:16 -sha1::64:20 - -#[PKCS] -#rsa::1024 -#dss::1024 - -[ServerInfo] -leevi.kuo.fi.ssh.com:10.2.1.7:Kuopio, Finland:1334 - -[AdminInfo] -Mun huone:Mun servo:Pekka Riikonen:priikone@poseidon.pspt.fi - -[ListenPort] -10.2.1.7:10.2.1.7:1334 - -[Logging] -infologfile:silcd.log:10000 -#warninglogfile:/var/log/silcd_warning.log:10000 -errorlogfile:leevi2_error.log:10000 -#fatallogfile:/var/log/silcd_error.log: - -[ConnectionClass] -1:100:100:100 -2:200:300:400 - -[ClientConnection] -10.2.1.199:passwd:priikone:333:1 -:::1333:1 - -[AdminConnection] -10.2.1.199:passwd:priikone:priikone:1 - -[ServerConnection] - -[RouterConnection] -10.2.1.7:passwd:priikone:1333:1:1 - -[DenyConnection] -[RedirectClient] diff --git a/apps/silcd/packet_receive.c b/apps/silcd/packet_receive.c new file mode 100644 index 00000000..099a4557 --- /dev/null +++ b/apps/silcd/packet_receive.c @@ -0,0 +1,2508 @@ +/* + + packet_receive.c + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2001 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. + +*/ +/* + * Server packet routines to handle received packets. + */ +/* $Id$ */ + +#include "serverincludes.h" +#include "server_internal.h" + +extern char *server_version; + +/* Received notify packet. Server can receive notify packets from router. + Server then relays the notify messages to clients if needed. */ + +void silc_server_notify(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcNotifyPayload payload; + SilcNotifyType type; + SilcArgumentPayload args; + SilcChannelID *channel_id = NULL, *channel_id2; + SilcClientID *client_id, *client_id2; + SilcServerID *server_id; + SilcChannelEntry channel; + SilcClientEntry client; + SilcServerEntry server_entry; + SilcChannelClientEntry chl; + SilcIDCacheEntry cache; + SilcHashTableList htl; + uint32 mode; + unsigned char *tmp; + uint32 tmp_len; + + SILC_LOG_DEBUG(("Start")); + + if (sock->type == SILC_SOCKET_TYPE_CLIENT || + packet->src_id_type != SILC_ID_SERVER) + return; + + if (!packet->dst_id) + return; + + /* If the packet is destined directly to a client then relay the packet + before processing it. */ + if (packet->dst_id_type == SILC_ID_CLIENT) { + SilcIDListData idata; + SilcSocketConnection dst_sock; + + /* Get the route to the client */ + dst_sock = silc_server_get_client_route(server, packet->dst_id, + packet->dst_id_len, NULL, &idata); + if (dst_sock) + /* Relay the packet */ + silc_server_relay_packet(server, dst_sock, idata->send_key, + idata->hmac_receive, idata->psn_send++, + packet, TRUE); + } + + /* Parse the Notify Payload */ + payload = silc_notify_payload_parse(packet->buffer); + if (!payload) + return; + + /* If we are router and this packet is not already broadcast packet + we will broadcast it. The sending socket really cannot be router or + the router is buggy. If this packet is coming from router then it must + have the broadcast flag set already and we won't do anything. */ + if (!server->standalone && server->server_type == SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_SERVER && + !(packet->flags & SILC_PACKET_FLAG_BROADCAST)) { + SILC_LOG_DEBUG(("Broadcasting received Notify packet")); + if (packet->dst_id_type == SILC_ID_CHANNEL) { + /* Packet is destined to channel */ + channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len, + packet->dst_id_type); + if (!channel_id) + goto out; + + silc_server_packet_send_dest(server, server->router->connection, + packet->type, + packet->flags | SILC_PACKET_FLAG_BROADCAST, + channel_id, SILC_ID_CHANNEL, + packet->buffer->data, packet->buffer->len, + FALSE); + silc_server_backup_send_dest(server, (SilcServerEntry)sock->user_data, + packet->type, packet->flags, + channel_id, SILC_ID_CHANNEL, + packet->buffer->data, packet->buffer->len, + FALSE, TRUE); + } else { + /* Packet is destined to client or server */ + silc_server_packet_send(server, server->router->connection, + packet->type, + packet->flags | SILC_PACKET_FLAG_BROADCAST, + packet->buffer->data, packet->buffer->len, + FALSE); + silc_server_backup_send(server, (SilcServerEntry)sock->user_data, + packet->type, packet->flags, + packet->buffer->data, packet->buffer->len, + FALSE, TRUE); + } + } + + type = silc_notify_get_type(payload); + args = silc_notify_get_args(payload); + if (!args) + goto out; + + switch(type) { + case SILC_NOTIFY_TYPE_JOIN: + /* + * Distribute the notify to local clients on the channel + */ + SILC_LOG_DEBUG(("JOIN notify")); + + /* 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); + if (!channel_id) + goto out; + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + silc_free(channel_id); + + /* 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); + if (!client_id) + goto out; + + /* If the the client is not in local list we check global list (ie. the + channel will be global channel) and if it does not exist then create + entry for the client. */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, server->server_type, + NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, server->server_type, + NULL); + if (!client) { + /* If router did not find the client the it is bogus */ + if (server->server_type != SILC_SERVER) + goto out; + + client = + silc_idlist_add_client(server->global_list, NULL, NULL, NULL, + silc_id_dup(client_id, SILC_ID_CLIENT), + sock->user_data, NULL); + if (!client) { + SILC_LOG_ERROR(("Could not add new client to the ID Cache")); + silc_free(client_id); + goto out; + } + + client->data.status |= SILC_IDLIST_STATUS_REGISTERED; + } + } + + /* Do not process the notify if the client is not registered */ + if (!(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) + break; + + /* Do not add client to channel if it is there already */ + if (silc_server_client_on_channel(client, channel)) { + SILC_LOG_DEBUG(("Client already on channel")); + break; + } + + /* Send to channel */ + silc_server_packet_send_to_channel(server, sock, channel, packet->type, + FALSE, packet->buffer->data, + packet->buffer->len, FALSE); + + if (server->server_type != SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_ROUTER) + /* The channel is global now */ + channel->global_users = TRUE; + + /* JOIN the global client to the channel (local clients (if router + created the channel) is joined in the pending JOIN command). */ + chl = silc_calloc(1, sizeof(*chl)); + chl->client = client; + chl->channel = channel; + + /* If this is the first one on the channel then it is the founder of + the channel. */ + if (!silc_hash_table_count(channel->user_list)) + chl->mode = (SILC_CHANNEL_UMODE_CHANOP | SILC_CHANNEL_UMODE_CHANFO); + + silc_hash_table_add(channel->user_list, client, chl); + silc_hash_table_add(client->channels, channel, chl); + silc_free(client_id); + + break; + + case SILC_NOTIFY_TYPE_LEAVE: + /* + * Distribute the notify to local clients on the channel + */ + SILC_LOG_DEBUG(("LEAVE notify")); + + if (!channel_id) { + channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len, + packet->dst_id_type); + if (!channel_id) + goto out; + } + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + + /* Get client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) { + silc_free(channel_id); + goto out; + } + client_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!client_id) { + silc_free(channel_id); + goto out; + } + + /* Get client entry */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!client) { + silc_free(client_id); + silc_free(channel_id); + goto out; + } + } + silc_free(client_id); + + /* Check if on channel */ + if (!silc_server_client_on_channel(client, channel)) + break; + + /* Send the leave notify to channel */ + silc_server_packet_send_to_channel(server, sock, channel, packet->type, + FALSE, packet->buffer->data, + packet->buffer->len, FALSE); + + /* Remove the user from channel */ + silc_server_remove_from_one_channel(server, sock, channel, client, FALSE); + break; + + case SILC_NOTIFY_TYPE_SIGNOFF: + /* + * Distribute the notify to local clients on the channel + */ + SILC_LOG_DEBUG(("SIGNOFF notify")); + + /* 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); + if (!client_id) + goto out; + + /* Get client entry */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, &cache); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, &cache); + if (!client) { + silc_free(client_id); + goto out; + } + } + silc_free(client_id); + + /* Get signoff message */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (tmp_len > 128) + tmp = NULL; + + /* Remove the client from all channels. */ + silc_server_remove_from_channels(server, NULL, client, TRUE, tmp, FALSE); + + client->data.status &= ~SILC_IDLIST_STATUS_REGISTERED; + cache->expire = SILC_ID_CACHE_EXPIRE_DEF; + server->stat.clients--; + if (server->server_type == SILC_ROUTER) + server->stat.cell_clients--; + break; + + case SILC_NOTIFY_TYPE_TOPIC_SET: + /* + * Distribute the notify to local clients on the channel + */ + + SILC_LOG_DEBUG(("TOPIC SET notify")); + + if (!channel_id) { + channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len, + packet->dst_id_type); + if (!channel_id) + goto out; + } + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + + /* Get the topic */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) { + silc_free(channel_id); + goto out; + } + + if (channel->topic) + silc_free(channel->topic); + channel->topic = silc_calloc(tmp_len + 1, sizeof(*channel->topic)); + memcpy(channel->topic, tmp, tmp_len); + + /* Send the same notify to the channel */ + silc_server_packet_send_to_channel(server, sock, channel, packet->type, + FALSE, packet->buffer->data, + packet->buffer->len, FALSE); + silc_free(channel_id); + break; + + case SILC_NOTIFY_TYPE_NICK_CHANGE: + { + /* + * Distribute the notify to local clients on the channel + */ + unsigned char *id, *id2; + + SILC_LOG_DEBUG(("NICK CHANGE notify")); + + /* Get old client ID */ + id = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!id) + goto out; + client_id = silc_id_payload_parse_id(id, tmp_len); + if (!client_id) + goto out; + + /* Get new client ID */ + id2 = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!id2) + goto out; + client_id2 = silc_id_payload_parse_id(id2, tmp_len); + if (!client_id2) + goto out; + + SILC_LOG_DEBUG(("Old Client ID id(%s)", + silc_id_render(client_id, SILC_ID_CLIENT))); + SILC_LOG_DEBUG(("New Client ID id(%s)", + silc_id_render(client_id2, SILC_ID_CLIENT))); + + /* Replace the Client ID */ + client = silc_idlist_replace_client_id(server->global_list, client_id, + client_id2); + if (!client) + client = silc_idlist_replace_client_id(server->local_list, client_id, + client_id2); + + if (client) { + /* The nickname is not valid anymore, set it NULL. This causes that + the nickname will be queried if someone wants to know it. */ + if (client->nickname) + silc_free(client->nickname); + client->nickname = NULL; + + /* Send the NICK_CHANGE notify type to local clients on the channels + this client is joined to. */ + silc_server_send_notify_on_channels(server, NULL, client, + SILC_NOTIFY_TYPE_NICK_CHANGE, 2, + id, tmp_len, + id2, tmp_len); + } + + silc_free(client_id); + if (!client) + silc_free(client_id2); + break; + } + + case SILC_NOTIFY_TYPE_CMODE_CHANGE: + /* + * Distribute the notify to local clients on the channel + */ + + SILC_LOG_DEBUG(("CMODE CHANGE notify")); + + if (!channel_id) { + channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len, + packet->dst_id_type); + if (!channel_id) + goto out; + } + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + + /* Get the mode */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) { + silc_free(channel_id); + goto out; + } + + SILC_GET32_MSB(mode, tmp); + + /* Check if mode changed */ + if (channel->mode == mode) + break; + + /* Send the same notify to the channel */ + silc_server_packet_send_to_channel(server, sock, channel, packet->type, + FALSE, packet->buffer->data, + packet->buffer->len, FALSE); + + /* If the channel had private keys set and the mode was removed then + we must re-generate and re-distribute a new channel key */ + if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY && + !(mode & SILC_CHANNEL_MODE_PRIVKEY)) { + /* Re-generate channel key */ + if (!silc_server_create_channel_key(server, channel, 0)) + goto out; + + /* Send the channel key. This sends it to our local clients and if + we are normal server to our router as well. */ + silc_server_send_channel_key(server, NULL, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); + } + + /* Change mode */ + channel->mode = mode; + silc_free(channel_id); + + /* Get the hmac */ + tmp = silc_argument_get_arg_type(args, 4, &tmp_len); + if (tmp) { + unsigned char hash[32]; + + if (channel->hmac) + silc_hmac_free(channel->hmac); + if (!silc_hmac_alloc(tmp, NULL, &channel->hmac)) + goto out; + + /* Set the HMAC key out of current channel key. The client must do + this locally. */ + silc_hash_make(silc_hmac_get_hash(channel->hmac), channel->key, + channel->key_len / 8, + hash); + silc_hmac_set_key(channel->hmac, hash, + silc_hash_len(silc_hmac_get_hash(channel->hmac))); + memset(hash, 0, sizeof(hash)); + } + + break; + + case SILC_NOTIFY_TYPE_CUMODE_CHANGE: + { + /* + * Distribute the notify to local clients on the channel + */ + SilcChannelClientEntry chl2 = NULL; + bool notify_sent = FALSE; + + SILC_LOG_DEBUG(("CUMODE CHANGE notify")); + + if (!channel_id) { + channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len, + packet->dst_id_type); + if (!channel_id) + goto out; + } + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + + /* Get the mode */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) { + silc_free(channel_id); + goto out; + } + + SILC_GET32_MSB(mode, tmp); + + /* Get target client */ + tmp = silc_argument_get_arg_type(args, 3, &tmp_len); + if (!tmp) + goto out; + client_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!client_id) + goto out; + + /* Get client entry */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!client) { + silc_free(client_id); + goto out; + } + } + silc_free(client_id); + + /* Get entry to the channel user list */ + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + /* If the mode is channel founder and we already find a client + to have that mode on the channel we will enforce the sender + to change the channel founder mode away. There can be only one + channel founder on the channel. */ + if (server->server_type == SILC_ROUTER && + mode & SILC_CHANNEL_UMODE_CHANFO && + chl->mode & SILC_CHANNEL_UMODE_CHANFO) { + SilcBuffer idp; + unsigned char cumode[4]; + + if (chl->client == client && chl->mode == mode) { + notify_sent = TRUE; + break; + } + + mode &= ~SILC_CHANNEL_UMODE_CHANFO; + silc_server_send_notify_cumode(server, sock, FALSE, channel, mode, + client->id, SILC_ID_CLIENT, + client->id); + + idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + SILC_PUT32_MSB(mode, cumode); + silc_server_send_notify_to_channel(server, sock, channel, FALSE, + SILC_NOTIFY_TYPE_CUMODE_CHANGE, + 3, idp->data, idp->len, + cumode, 4, + idp->data, idp->len); + silc_buffer_free(idp); + notify_sent = TRUE; + + /* Force the mode change if we alredy set the mode */ + if (chl2) { + chl2->mode = mode; + silc_free(channel_id); + goto out; + } + } + + if (chl->client == client) { + if (chl->mode == mode) { + notify_sent = TRUE; + break; + } + + /* Change the mode */ + chl->mode = mode; + if (!(mode & SILC_CHANNEL_UMODE_CHANFO)) + break; + + chl2 = chl; + } + } + + /* Send the same notify to the channel */ + if (!notify_sent) + silc_server_packet_send_to_channel(server, sock, channel, + packet->type, + FALSE, packet->buffer->data, + packet->buffer->len, FALSE); + + silc_free(channel_id); + break; + } + + case SILC_NOTIFY_TYPE_INVITE: + + if (packet->dst_id_type == SILC_ID_CLIENT) + goto out; + + SILC_LOG_DEBUG(("INVITE notify")); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id) + goto out; + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + silc_free(channel_id); + + /* Get the added invite */ + tmp = silc_argument_get_arg_type(args, 3, &tmp_len); + if (tmp) { + if (!channel->invite_list) + channel->invite_list = silc_calloc(tmp_len + 2, + sizeof(*channel->invite_list)); + else + channel->invite_list = silc_realloc(channel->invite_list, + sizeof(*channel->invite_list) * + (tmp_len + + strlen(channel->invite_list) + + 2)); + if (tmp[tmp_len - 1] == ',') + tmp[tmp_len - 1] = '\0'; + + strncat(channel->invite_list, tmp, tmp_len); + strncat(channel->invite_list, ",", 1); + } + + /* Get the deleted invite */ + tmp = silc_argument_get_arg_type(args, 4, &tmp_len); + if (tmp && channel->invite_list) { + char *start, *end, *n; + + if (!strncmp(channel->invite_list, tmp, + strlen(channel->invite_list) - 1)) { + silc_free(channel->invite_list); + channel->invite_list = NULL; + } else { + start = strstr(channel->invite_list, tmp); + if (start && strlen(start) >= tmp_len) { + end = start + tmp_len; + n = silc_calloc(strlen(channel->invite_list) - tmp_len, sizeof(*n)); + strncat(n, channel->invite_list, start - channel->invite_list); + strncat(n, end + 1, ((channel->invite_list + + strlen(channel->invite_list)) - end) - 1); + silc_free(channel->invite_list); + channel->invite_list = n; + } + } + } + + break; + + case SILC_NOTIFY_TYPE_CHANNEL_CHANGE: + /* + * Distribute to the local clients on the channel and change the + * channel ID. + */ + + SILC_LOG_DEBUG(("CHANNEL CHANGE")); + + if (sock->type != SILC_SOCKET_TYPE_ROUTER) + break; + + /* Get the old Channel ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id) + goto out; + + /* Get the channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + + /* Send the notify to the channel */ + silc_server_packet_send_to_channel(server, sock, channel, packet->type, + FALSE, packet->buffer->data, + packet->buffer->len, FALSE); + + /* Get the new Channel ID */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) + goto out; + channel_id2 = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id2) + goto out; + + SILC_LOG_DEBUG(("Old Channel ID id(%s)", + silc_id_render(channel_id, SILC_ID_CHANNEL))); + SILC_LOG_DEBUG(("New Channel ID id(%s)", + silc_id_render(channel_id2, SILC_ID_CHANNEL))); + + /* Replace the Channel ID */ + if (!silc_idlist_replace_channel_id(server->global_list, channel_id, + channel_id2)) + if (!silc_idlist_replace_channel_id(server->local_list, channel_id, + channel_id2)) { + silc_free(channel_id2); + channel_id2 = NULL; + } + + if (channel_id2) { + SilcBuffer users = NULL, users_modes = NULL; + + /* Re-announce our clients on the channel as the ID has changed now */ + silc_server_announce_get_channel_users(server, channel, &users, + &users_modes); + if (users) { + silc_buffer_push(users, users->data - users->head); + silc_server_packet_send(server, sock, + SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST, + users->data, users->len, FALSE); + silc_buffer_free(users); + } + if (users_modes) { + silc_buffer_push(users_modes, users_modes->data - users_modes->head); + silc_server_packet_send_dest(server, sock, + SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST, + channel->id, SILC_ID_CHANNEL, + users_modes->data, + users_modes->len, FALSE); + silc_buffer_free(users_modes); + } + } + + silc_free(channel_id); + + break; + + case SILC_NOTIFY_TYPE_SERVER_SIGNOFF: + /* + * Remove the server entry and all clients that this server owns. + */ + + SILC_LOG_DEBUG(("SERVER SIGNOFF notify")); + + /* Get Server ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + server_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!server_id) + goto out; + + /* Get server entry */ + server_entry = silc_idlist_find_server_by_id(server->global_list, + server_id, TRUE, NULL); + if (!server_entry) { + server_entry = silc_idlist_find_server_by_id(server->local_list, + server_id, TRUE, NULL); + if (!server_entry) { + /* If we are normal server then we might not have the server. Check + whether router was kind enough to send the list of all clients + that actually was to be removed. Remove them if the list is + available. */ + if (server->server_type != SILC_ROUTER && + silc_argument_get_arg_num(args) > 1) { + int i; + + for (i = 1; i < silc_argument_get_arg_num(args); i++) { + /* Get Client ID */ + tmp = silc_argument_get_arg_type(args, i + 1, &tmp_len); + if (!tmp) + continue; + client_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!client_id) + continue; + + /* Get client entry */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, &cache); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, &cache); + if (!client) { + silc_free(client_id); + continue; + } + } + silc_free(client_id); + + /* Remove the client from all channels. */ + silc_server_remove_from_channels(server, NULL, client, + TRUE, NULL, FALSE); + + client->data.status &= ~SILC_IDLIST_STATUS_REGISTERED; + cache->expire = SILC_ID_CACHE_EXPIRE_DEF; + server->stat.clients--; + if (server->server_type == SILC_ROUTER) + server->stat.cell_clients--; + } + } + + silc_free(server_id); + goto out; + } + } + silc_free(server_id); + + /* Free all client entries that this server owns as they will + become invalid now as well. */ + silc_server_remove_clients_by_server(server, server_entry, TRUE); + + /* Remove the server entry */ + if (!silc_idlist_del_server(server->global_list, server_entry)) + silc_idlist_del_server(server->local_list, server_entry); + + /* XXX update statistics */ + + break; + + case SILC_NOTIFY_TYPE_KICKED: + /* + * Distribute the notify to local clients on the channel + */ + + SILC_LOG_DEBUG(("KICKED notify")); + + if (!channel_id) { + channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len, + packet->dst_id_type); + if (!channel_id) + goto out; + } + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + silc_free(channel_id); + + /* 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); + if (!client_id) + goto out; + + /* If the the client is not in local list we check global list */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!client) { + silc_free(client_id); + goto out; + } + } + + /* Send to channel */ + silc_server_packet_send_to_channel(server, sock, channel, packet->type, + FALSE, packet->buffer->data, + packet->buffer->len, FALSE); + + /* Remove the client from channel */ + silc_server_remove_from_one_channel(server, sock, channel, client, FALSE); + + break; + + case SILC_NOTIFY_TYPE_KILLED: + { + /* + * Distribute the notify to local clients on channels + */ + unsigned char *id; + uint32 id_len; + + SILC_LOG_DEBUG(("KILLED notify")); + + /* Get client ID */ + id = silc_argument_get_arg_type(args, 1, &id_len); + if (!id) + goto out; + client_id = silc_id_payload_parse_id(id, id_len); + if (!client_id) + goto out; + + /* If the the client is not in local list we check global list */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!client) { + silc_free(client_id); + goto out; + } + } + silc_free(client_id); + + /* If the client is one of ours, then close the connection to the + client now. This removes the client from all channels as well. */ + if (packet->dst_id_type == SILC_ID_CLIENT && client->connection) { + sock = client->connection; + silc_server_free_client_data(server, NULL, client, FALSE, NULL); + silc_server_close_connection(server, sock); + break; + } + + /* Get comment */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (tmp_len > 128) + tmp = NULL; + + /* Send the notify to local clients on the channels except to the + client who is killed. */ + silc_server_send_notify_on_channels(server, client, client, + SILC_NOTIFY_TYPE_KILLED, + tmp ? 2 : 1, + id, id_len, + tmp, tmp_len); + + /* Remove the client from all channels */ + silc_server_remove_from_channels(server, NULL, client, FALSE, NULL, + FALSE); + + break; + } + + case SILC_NOTIFY_TYPE_UMODE_CHANGE: + /* + * Save the mode of the client. + */ + + SILC_LOG_DEBUG(("UMODE_CHANGE notify")); + + /* 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); + if (!client_id) + goto out; + + /* Get client entry */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, TRUE, NULL); + if (!client) { + silc_free(client_id); + goto out; + } + } + silc_free(client_id); + + /* Get the mode */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (!tmp) + goto out; + + /* Save the mode */ + SILC_GET32_MSB(client->mode, tmp); + + break; + + case SILC_NOTIFY_TYPE_BAN: + /* + * Save the ban + */ + + SILC_LOG_DEBUG(("BAN notify")); + + /* Get Channel ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + channel_id = silc_id_payload_parse_id(tmp, tmp_len); + if (!channel_id) + goto out; + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->global_list, + channel_id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + } + silc_free(channel_id); + + /* Get the new ban and add it to the ban list */ + tmp = silc_argument_get_arg_type(args, 2, &tmp_len); + if (tmp) { + if (!channel->ban_list) + channel->ban_list = silc_calloc(tmp_len + 2, + sizeof(*channel->ban_list)); + else + channel->ban_list = silc_realloc(channel->ban_list, + sizeof(*channel->ban_list) * + (tmp_len + + strlen(channel->ban_list) + 2)); + strncat(channel->ban_list, tmp, tmp_len); + strncat(channel->ban_list, ",", 1); + } + + /* Get the ban to be removed and remove it from the list */ + tmp = silc_argument_get_arg_type(args, 3, &tmp_len); + if (tmp && channel->ban_list) { + char *start, *end, *n; + + if (!strcmp(channel->ban_list, tmp)) { + silc_free(channel->ban_list); + channel->ban_list = NULL; + } else { + start = strstr(channel->ban_list, tmp); + if (start && strlen(start) >= tmp_len) { + end = start + tmp_len; + n = silc_calloc(strlen(channel->ban_list) - tmp_len, sizeof(*n)); + strncat(n, channel->ban_list, start - channel->ban_list); + strncat(n, end + 1, ((channel->ban_list + + strlen(channel->ban_list)) - end) - 1); + silc_free(channel->ban_list); + channel->ban_list = n; + } + } + } + + break; + + /* Ignore rest of the notify types for now */ + case SILC_NOTIFY_TYPE_NONE: + case SILC_NOTIFY_TYPE_MOTD: + break; + default: + break; + } + + out: + silc_notify_payload_free(payload); +} + +void silc_server_notify_list(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcPacketContext *new; + SilcBuffer buffer; + uint16 len; + + SILC_LOG_DEBUG(("Processing Notify List")); + + if (sock->type == SILC_SOCKET_TYPE_CLIENT || + packet->src_id_type != SILC_ID_SERVER) + return; + + /* Make copy of the original packet context, except for the actual + data buffer, which we will here now fetch from the original buffer. */ + new = silc_packet_context_alloc(); + new->type = SILC_PACKET_NOTIFY; + new->flags = packet->flags; + new->src_id = packet->src_id; + new->src_id_len = packet->src_id_len; + new->src_id_type = packet->src_id_type; + new->dst_id = packet->dst_id; + new->dst_id_len = packet->dst_id_len; + new->dst_id_type = packet->dst_id_type; + + buffer = silc_buffer_alloc(1024); + new->buffer = buffer; + + while (packet->buffer->len) { + SILC_GET16_MSB(len, packet->buffer->data + 2); + if (len > packet->buffer->len) + break; + + if (len > buffer->truelen) { + silc_buffer_free(buffer); + buffer = silc_buffer_alloc(1024 + len); + } + + silc_buffer_pull_tail(buffer, len); + silc_buffer_put(buffer, packet->buffer->data, len); + + /* Process the Notify */ + silc_server_notify(server, sock, new); + + silc_buffer_push_tail(buffer, len); + silc_buffer_pull(packet->buffer, len); + } + + silc_buffer_free(buffer); + silc_free(new); +} + +/* Received private message. This resolves the destination of the message + and sends the packet. This is used by both server and router. If the + destination is our locally connected client this sends the packet to + the client. This may also send the message for further routing if + the destination is not in our server (or router). */ + +void silc_server_private_message(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcSocketConnection dst_sock; + SilcIDListData idata; + + SILC_LOG_DEBUG(("Start")); + + if (packet->src_id_type != SILC_ID_CLIENT || + packet->dst_id_type != SILC_ID_CLIENT) + return; + + if (!packet->dst_id) + return; + + /* Get the route to the client */ + dst_sock = silc_server_get_client_route(server, packet->dst_id, + packet->dst_id_len, NULL, &idata); + if (!dst_sock) + return; + + /* Send the private message */ + silc_server_send_private_message(server, dst_sock, idata->send_key, + idata->hmac_send, idata->psn_send++, + packet); +} + +/* Received private message key packet.. This packet is never for us. It is to + the client in the packet's destination ID. Sending of this sort of packet + equals sending private message, ie. it is sent point to point from + one client to another. */ + +void silc_server_private_message_key(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcSocketConnection dst_sock; + SilcIDListData idata; + + SILC_LOG_DEBUG(("Start")); + + if (packet->src_id_type != SILC_ID_CLIENT || + packet->dst_id_type != SILC_ID_CLIENT) + return; + + if (!packet->dst_id) + return; + + /* Get the route to the client */ + dst_sock = silc_server_get_client_route(server, packet->dst_id, + packet->dst_id_len, NULL, &idata); + if (!dst_sock) + return; + + /* Relay the packet */ + silc_server_relay_packet(server, dst_sock, idata->send_key, + idata->hmac_send, idata->psn_send++, packet, FALSE); +} + +/* Processes incoming command reply packet. The command reply packet may + be destined to one of our clients or it may directly for us. We will + call the command reply routine after processing the packet. */ + +void silc_server_command_reply(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcClientEntry client = NULL; + SilcSocketConnection dst_sock; + SilcIDListData idata; + SilcClientID *id = NULL; + + SILC_LOG_DEBUG(("Start")); + + /* Source must be server or router */ + if (packet->src_id_type != SILC_ID_SERVER && + sock->type != SILC_SOCKET_TYPE_ROUTER) + return; + + if (packet->dst_id_type == SILC_ID_CHANNEL) + return; + + if (packet->dst_id_type == SILC_ID_CLIENT) { + /* Destination must be one of ours */ + id = silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CLIENT); + if (!id) + return; + client = silc_idlist_find_client_by_id(server->local_list, id, TRUE, NULL); + if (!client) { + SILC_LOG_ERROR(("Cannot process command reply to unknown client")); + silc_free(id); + return; + } + } + + if (packet->dst_id_type == SILC_ID_SERVER) { + /* For now this must be for us */ + if (memcmp(packet->dst_id, server->id_string, packet->dst_id_len)) { + SILC_LOG_ERROR(("Cannot process command reply to unknown server")); + return; + } + } + + /* Execute command reply locally for the command */ + silc_server_command_reply_process(server, sock, buffer); + + if (packet->dst_id_type == SILC_ID_CLIENT && client && id) { + /* Relay the packet to the client */ + + dst_sock = (SilcSocketConnection)client->connection; + silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len + + packet->dst_id_len + packet->padlen); + + silc_packet_send_prepare(dst_sock, 0, 0, buffer->len); + silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); + + idata = (SilcIDListData)client; + + /* Encrypt packet */ + silc_packet_encrypt(idata->send_key, idata->hmac_send, idata->psn_send++, + dst_sock->outbuf, buffer->len); + + /* Send the packet */ + silc_server_packet_send_real(server, dst_sock, TRUE); + + silc_free(id); + } +} + +/* Process received channel message. The message can be originated from + client or server. */ + +void silc_server_channel_message(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcChannelEntry channel = NULL; + SilcChannelID *id = NULL; + void *sender = NULL; + void *sender_entry = NULL; + bool local = TRUE; + + SILC_LOG_DEBUG(("Processing channel message")); + + /* Sanity checks */ + if (packet->dst_id_type != SILC_ID_CHANNEL) { + SILC_LOG_DEBUG(("Received bad message for channel, dropped")); + goto out; + } + + /* Find channel entry */ + id = silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL); + if (!id) + goto out; + channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL); + if (!channel) { + SILC_LOG_DEBUG(("Could not find channel")); + goto out; + } + } + + /* See that this client is on the channel. If the original sender is + not client (as it can be server as well) we don't do the check. */ + sender = silc_id_str2id(packet->src_id, packet->src_id_len, + packet->src_id_type); + if (!sender) + goto out; + if (packet->src_id_type == SILC_ID_CLIENT) { + sender_entry = silc_idlist_find_client_by_id(server->local_list, + sender, TRUE, NULL); + if (!sender_entry) { + local = FALSE; + sender_entry = silc_idlist_find_client_by_id(server->global_list, + sender, TRUE, NULL); + } + if (!sender_entry || !silc_server_client_on_channel(sender_entry, + channel)) { + SILC_LOG_DEBUG(("Client not on channel")); + goto out; + } + + /* If the packet is coming from router, but the client entry is + local entry to us then some router is rerouting this to us and it is + not allowed. */ + if (server->server_type == SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_ROUTER && local) { + SILC_LOG_DEBUG(("Channel message rerouted to the sender, drop it")); + goto out; + } + } + + /* Distribute the packet to our local clients. This will send the + packet for further routing as well, if needed. */ + silc_server_packet_relay_to_channel(server, sock, channel, sender, + packet->src_id_type, sender_entry, + packet->buffer->data, + packet->buffer->len, FALSE); + + out: + if (sender) + silc_free(sender); + if (id) + silc_free(id); +} + +/* Received channel key packet. We distribute the key to all of our locally + connected clients on the channel. */ + +void silc_server_channel_key(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcChannelEntry channel; + + if (packet->src_id_type != SILC_ID_SERVER || + (server->server_type == SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_ROUTER)) + return; + + /* Save the channel key */ + channel = silc_server_save_channel_key(server, buffer, NULL); + if (!channel) + return; + + /* Distribute the key to everybody who is on the channel. If we are router + we will also send it to locally connected servers. */ + silc_server_send_channel_key(server, sock, channel, FALSE); + + if (server->server_type != SILC_BACKUP_ROUTER) { + /* Distribute to local cell backup routers. */ + silc_server_backup_send(server, (SilcServerEntry)sock->user_data, + SILC_PACKET_CHANNEL_KEY, 0, + buffer->data, buffer->len, FALSE, TRUE); + } +} + +/* Received New Client packet and processes it. Creates Client ID for the + client. Client becomes registered after calling this functions. */ + +SilcClientEntry silc_server_new_client(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcClientEntry client; + SilcClientID *client_id; + SilcBuffer reply; + SilcIDListData idata; + char *username = NULL, *realname = NULL, *id_string; + uint32 id_len; + int ret; + char *hostname, *nickname; + int nickfail = 0; + + SILC_LOG_DEBUG(("Creating new client")); + + if (sock->type != SILC_SOCKET_TYPE_CLIENT) + return NULL; + + /* Take client entry */ + client = (SilcClientEntry)sock->user_data; + idata = (SilcIDListData)client; + + /* Remove the old cache entry */ + if (!silc_idcache_del_by_context(server->local_list->clients, client)) { + SILC_LOG_ERROR(("Lost client's cache entry - bad thing")); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Unknown client"); + return NULL; + } + + /* Parse incoming packet */ + ret = silc_buffer_unformat(buffer, + SILC_STR_UI16_STRING_ALLOC(&username), + SILC_STR_UI16_STRING_ALLOC(&realname), + SILC_STR_END); + if (ret == -1) { + if (username) + silc_free(username); + if (realname) + silc_free(realname); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Incomplete client information"); + return NULL; + } + + if (!username) { + silc_free(username); + if (realname) + silc_free(realname); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Incomplete client information"); + return NULL; + } + + if (strlen(username) > 128) + username[127] = '\0'; + + nickname = strdup(username); + + /* Make sanity checks for the hostname of the client. If the hostname + is provided in the `username' check that it is the same than the + resolved hostname, or if not resolved the hostname that appears in + the client's public key. If the hostname is not present then put + it from the resolved name or from the public key. */ + if (strchr(username, '@')) { + SilcPublicKeyIdentifier pident; + int tlen = strcspn(username, "@"); + char *phostname = NULL; + + hostname = silc_calloc((strlen(username) - tlen) + 1, sizeof(char)); + memcpy(hostname, username + tlen + 1, strlen(username) - tlen - 1); + + if (strcmp(sock->hostname, sock->ip) && + strcmp(sock->hostname, hostname)) { + silc_free(username); + silc_free(hostname); + if (realname) + silc_free(realname); + silc_server_disconnect_remote(server, sock, + "Server closed connection: " + "Incomplete client information"); + return NULL; + } + + pident = silc_pkcs_decode_identifier(client->data.public_key->identifier); + if (pident) { + phostname = strdup(pident->host); + silc_pkcs_free_identifier(pident); + } + + if (!strcmp(sock->hostname, sock->ip) && + phostname && strcmp(phostname, hostname)) { + silc_free(username); + silc_free(hostname); + if (phostname) + silc_free(phostname); + if (realname) + silc_free(realname); + silc_server_disconnect_remote(server, sock, + "Server closed connection: " + "Incomplete client information"); + return NULL; + } + + if (phostname) + silc_free(phostname); + } else { + /* The hostname is not present, add it. */ + char *newusername; + /* XXX For now we cannot take the host name from the public key since + they are not trusted or we cannot verify them as trusted. Just take + what the resolved name or address is. */ +#if 0 + if (strcmp(sock->hostname, sock->ip)) { +#endif + newusername = silc_calloc(strlen(username) + + strlen(sock->hostname) + 2, + sizeof(*newusername)); + strncat(newusername, username, strlen(username)); + strncat(newusername, "@", 1); + strncat(newusername, sock->hostname, strlen(sock->hostname)); + silc_free(username); + username = newusername; +#if 0 + } else { + SilcPublicKeyIdentifier pident = + silc_pkcs_decode_identifier(client->data.public_key->identifier); + + if (pident) { + newusername = silc_calloc(strlen(username) + + strlen(pident->host) + 2, + sizeof(*newusername)); + strncat(newusername, username, strlen(username)); + strncat(newusername, "@", 1); + strncat(newusername, pident->host, strlen(pident->host)); + silc_free(username); + username = newusername; + silc_pkcs_free_identifier(pident); + } + } +#endif + } + + /* Create Client ID */ + while (!silc_id_create_client_id(server, server->id, server->rng, + server->md5hash, nickname, &client_id)) { + nickfail++; + snprintf(&nickname[strlen(nickname) - 1], 1, "%d", nickfail); + } + + /* Update client entry */ + idata->status |= SILC_IDLIST_STATUS_REGISTERED; + client->nickname = nickname; + client->username = username; + client->userinfo = realname ? realname : strdup(" "); + client->id = client_id; + id_len = silc_id_get_len(client_id, SILC_ID_CLIENT); + + /* Add the client again to the ID cache */ + silc_idcache_add(server->local_list->clients, client->nickname, + client_id, client, FALSE); + + /* Notify our router about new client on the SILC network */ + if (!server->standalone) + silc_server_send_new_id(server, (SilcSocketConnection) + server->router->connection, + server->server_type == SILC_ROUTER ? TRUE : FALSE, + client->id, SILC_ID_CLIENT, id_len); + + /* Send the new client ID to the client. */ + id_string = silc_id_id2str(client->id, SILC_ID_CLIENT); + reply = silc_buffer_alloc(2 + 2 + id_len); + silc_buffer_pull_tail(reply, SILC_BUFFER_END(reply)); + silc_buffer_format(reply, + SILC_STR_UI_SHORT(SILC_ID_CLIENT), + SILC_STR_UI_SHORT(id_len), + SILC_STR_UI_XNSTRING(id_string, id_len), + SILC_STR_END); + silc_server_packet_send(server, sock, SILC_PACKET_NEW_ID, 0, + reply->data, reply->len, FALSE); + silc_free(id_string); + silc_buffer_free(reply); + + /* Send some nice info to the client */ + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("Welcome to the SILC Network %s", + username)); + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("Your host is %s, running version %s", + server->config->server_info->server_name, + server_version)); + if (server->server_type == SILC_ROUTER) { + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("There are %d clients on %d servers in SILC " + "Network", server->stat.clients, + server->stat.servers + 1)); + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("There are %d clients on %d server in our cell", + server->stat.cell_clients, + server->stat.cell_servers + 1)); + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("I have %d clients, %d channels, %d servers and " + "%d routers", + server->stat.my_clients, + server->stat.my_channels, + server->stat.my_servers, + server->stat.my_routers)); + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("%d server operators and %d router operators " + "online", + server->stat.my_server_ops, + server->stat.my_router_ops)); + } else { + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("I have %d clients and %d channels formed", + server->stat.my_clients, + server->stat.my_channels)); + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("%d operators online", + server->stat.my_server_ops)); + } + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("Your connection is secured with %s cipher, " + "key length %d bits", + idata->send_key->cipher->name, + idata->send_key->cipher->key_len)); + SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE, + ("Your current nickname is %s", + client->nickname)); + + /* Send motd */ + silc_server_send_motd(server, sock); + + return client; +} + +/* Create new server. This processes received New Server packet and + saves the received Server ID. The server is our locally connected + server thus we save all the information and save it to local list. + This funtion can be used by both normal server and router server. + If normal server uses this it means that its router has connected + to the server. If router uses this it means that one of the cell's + servers is connected to the router. */ + +SilcServerEntry silc_server_new_server(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcServerEntry new_server, server_entry; + SilcServerID *server_id; + SilcIDListData idata; + unsigned char *server_name, *id_string; + uint16 id_len, name_len; + int ret; + bool local = TRUE; + + SILC_LOG_DEBUG(("Creating new server")); + + if (sock->type != SILC_SOCKET_TYPE_SERVER && + sock->type != SILC_SOCKET_TYPE_ROUTER) + return NULL; + + /* Take server entry */ + new_server = (SilcServerEntry)sock->user_data; + idata = (SilcIDListData)new_server; + + /* Remove the old cache entry */ + if (!silc_idcache_del_by_context(server->local_list->servers, new_server)) { + silc_idcache_del_by_context(server->global_list->servers, new_server); + local = FALSE; + } + + /* Parse the incoming packet */ + ret = silc_buffer_unformat(buffer, + SILC_STR_UI16_NSTRING_ALLOC(&id_string, &id_len), + SILC_STR_UI16_NSTRING_ALLOC(&server_name, + &name_len), + SILC_STR_END); + if (ret == -1) { + if (id_string) + silc_free(id_string); + if (server_name) + silc_free(server_name); + return NULL; + } + + if (id_len > buffer->len) { + silc_free(id_string); + silc_free(server_name); + return NULL; + } + + if (name_len > 256) + server_name[255] = '\0'; + + /* Get Server ID */ + server_id = silc_id_str2id(id_string, id_len, SILC_ID_SERVER); + if (!server_id) { + silc_free(id_string); + silc_free(server_name); + return NULL; + } + silc_free(id_string); + + /* Check that we do not have this ID already */ + server_entry = silc_idlist_find_server_by_id(server->local_list, + server_id, TRUE, NULL); + if (server_entry) { + silc_idcache_del_by_context(server->local_list->servers, server_entry); + } else { + server_entry = silc_idlist_find_server_by_id(server->global_list, + server_id, TRUE, NULL); + if (server_entry) + silc_idcache_del_by_context(server->global_list->servers, server_entry); + } + + /* Update server entry */ + idata->status |= SILC_IDLIST_STATUS_REGISTERED; + new_server->server_name = server_name; + new_server->id = server_id; + + SILC_LOG_DEBUG(("New server id(%s)", + silc_id_render(server_id, SILC_ID_SERVER))); + + /* Add again the entry to the ID cache. */ + silc_idcache_add(local ? server->local_list->servers : + server->global_list->servers, server_name, server_id, + new_server, FALSE); + + /* Distribute the information about new server in the SILC network + to our router. If we are normal server we won't send anything + since this connection must be our router connection. */ + if (server->server_type == SILC_ROUTER && !server->standalone && + server->router->connection != sock) + silc_server_send_new_id(server, server->router->connection, + TRUE, new_server->id, SILC_ID_SERVER, + silc_id_get_len(server_id, SILC_ID_SERVER)); + + if (server->server_type == SILC_ROUTER) + server->stat.cell_servers++; + + /* Check whether this router connection has been replaced by an + backup router. If it has been then we'll disable the server and will + ignore everything it will send until the backup router resuming + protocol has been completed. */ + if (sock->type == SILC_SOCKET_TYPE_ROUTER && + silc_server_backup_replaced_get(server, server_id, NULL)) { + /* Send packet to the server indicating that it cannot use this + connection as it has been replaced by backup router. */ + SilcBuffer packet = silc_buffer_alloc(2); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + silc_buffer_format(packet, + SILC_STR_UI_CHAR(SILC_SERVER_BACKUP_REPLACED), + SILC_STR_UI_CHAR(0), + SILC_STR_END); + silc_server_packet_send(server, sock, + SILC_PACKET_RESUME_ROUTER, 0, + packet->data, packet->len, TRUE); + silc_buffer_free(packet); + + /* Mark the router disabled. The data sent earlier will go but nothing + after this does not go to this connection. */ + idata->status |= SILC_IDLIST_STATUS_DISABLED; + } else { + /* If it is router announce our stuff to it. */ + if (sock->type == SILC_SOCKET_TYPE_ROUTER && + server->server_type == SILC_ROUTER) { + silc_server_announce_servers(server, FALSE, 0, sock); + silc_server_announce_clients(server, 0, sock); + silc_server_announce_channels(server, 0, sock); + } + } + + return new_server; +} + +/* Processes incoming New ID packet. New ID Payload is used to distribute + information about newly registered clients and servers. */ + +static void silc_server_new_id_real(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet, + int broadcast) +{ + SilcBuffer buffer = packet->buffer; + SilcIDList id_list; + SilcServerEntry router, server_entry; + SilcSocketConnection router_sock; + SilcIDPayload idp; + SilcIdType id_type; + void *id; + + SILC_LOG_DEBUG(("Processing new ID")); + + if (sock->type == SILC_SOCKET_TYPE_CLIENT || + server->server_type == SILC_SERVER || + packet->src_id_type != SILC_ID_SERVER) + return; + + idp = silc_id_payload_parse(buffer); + if (!idp) + return; + + id_type = silc_id_payload_get_type(idp); + + /* Normal server cannot have other normal server connections */ + server_entry = (SilcServerEntry)sock->user_data; + if (id_type == SILC_ID_SERVER && sock->type == SILC_SOCKET_TYPE_SERVER && + server_entry->server_type == SILC_SERVER) + goto out; + + id = silc_id_payload_get_id(idp); + if (!id) + goto out; + + /* If the packet is coming from server then use the sender as the + origin of the the packet. If it came from router then check the real + sender of the packet and use that as the origin. */ + if (sock->type == SILC_SOCKET_TYPE_SERVER) { + id_list = server->local_list; + router_sock = sock; + router = sock->user_data; + + /* If the sender is backup router and ID is server (and we are not + backup router) then switch the entry to global list. */ + if (server_entry->server_type == SILC_BACKUP_ROUTER && + id_type == SILC_ID_SERVER && + server->id_entry->server_type != SILC_BACKUP_ROUTER) { + id_list = server->global_list; + router_sock = server->router ? server->router->connection : sock; + } + } else { + void *sender_id = silc_id_str2id(packet->src_id, packet->src_id_len, + packet->src_id_type); + router = silc_idlist_find_server_by_id(server->global_list, + sender_id, TRUE, NULL); + if (!router) + router = silc_idlist_find_server_by_id(server->local_list, + sender_id, TRUE, NULL); + silc_free(sender_id); + if (!router) + goto out; + router_sock = sock; + id_list = server->global_list; + } + + switch(id_type) { + case SILC_ID_CLIENT: + { + SilcClientEntry entry; + + /* Check that we do not have this client already */ + entry = silc_idlist_find_client_by_id(server->global_list, + id, server->server_type, + NULL); + if (!entry) + entry = silc_idlist_find_client_by_id(server->local_list, + id, server->server_type, + NULL); + if (entry) { + SILC_LOG_DEBUG(("Ignoring client that we already have")); + goto out; + } + + SILC_LOG_DEBUG(("New client id(%s) from [%s] %s", + silc_id_render(id, SILC_ID_CLIENT), + sock->type == SILC_SOCKET_TYPE_SERVER ? + "Server" : "Router", sock->hostname)); + + /* As a router we keep information of all global information in our + global list. Cell wide information however is kept in the local + list. */ + entry = silc_idlist_add_client(id_list, NULL, NULL, NULL, + id, router, NULL); + if (!entry) { + SILC_LOG_ERROR(("Could not add new client to the ID Cache")); + + /* Inform the sender that the ID is not usable */ + silc_server_send_notify_signoff(server, sock, FALSE, id, NULL); + goto out; + } + entry->nickname = NULL; + entry->data.status |= SILC_IDLIST_STATUS_REGISTERED; + + if (sock->type == SILC_SOCKET_TYPE_SERVER) + server->stat.cell_clients++; + server->stat.clients++; + } + break; + + case SILC_ID_SERVER: + { + SilcServerEntry entry; + + /* If the ID is mine, ignore it. */ + if (SILC_ID_SERVER_COMPARE(id, server->id)) { + SILC_LOG_DEBUG(("Ignoring my own ID as new ID")); + break; + } + + /* If the ID is the sender's ID, ignore it (we have it already) */ + if (SILC_ID_SERVER_COMPARE(id, router->id)) { + SILC_LOG_DEBUG(("Ignoring sender's own ID")); + break; + } + + /* Check that we do not have this server already */ + entry = silc_idlist_find_server_by_id(server->global_list, + id, server->server_type, + NULL); + if (!entry) + entry = silc_idlist_find_server_by_id(server->local_list, + id, server->server_type, + NULL); + if (entry) { + SILC_LOG_DEBUG(("Ignoring server that we already have")); + goto out; + } + + SILC_LOG_DEBUG(("New server id(%s) from [%s] %s", + silc_id_render(id, SILC_ID_SERVER), + sock->type == SILC_SOCKET_TYPE_SERVER ? + "Server" : "Router", sock->hostname)); + + /* As a router we keep information of all global information in our + global list. Cell wide information however is kept in the local + list. */ + entry = silc_idlist_add_server(id_list, NULL, 0, id, router, + router_sock); + if (!entry) { + SILC_LOG_ERROR(("Could not add new server to the ID Cache")); + goto out; + } + entry->data.status |= SILC_IDLIST_STATUS_REGISTERED; + + if (sock->type == SILC_SOCKET_TYPE_SERVER) + server->stat.cell_servers++; + server->stat.servers++; + } + break; + + case SILC_ID_CHANNEL: + SILC_LOG_ERROR(("Channel cannot be registered with NEW_ID packet")); + goto out; + break; + + default: + goto out; + break; + } + + /* If the sender of this packet is server and we are router we need to + broadcast this packet to other routers in the network. */ + if (broadcast && !server->standalone && server->server_type == SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_SERVER && + !(packet->flags & SILC_PACKET_FLAG_BROADCAST)) { + SILC_LOG_DEBUG(("Broadcasting received New ID packet")); + silc_server_packet_send(server, server->router->connection, + packet->type, + packet->flags | SILC_PACKET_FLAG_BROADCAST, + buffer->data, buffer->len, FALSE); + silc_server_backup_send(server, (SilcServerEntry)sock->user_data, + packet->type, packet->flags, + packet->buffer->data, packet->buffer->len, + FALSE, TRUE); + } + + out: + silc_id_payload_free(idp); +} + + +/* Processes incoming New ID packet. New ID Payload is used to distribute + information about newly registered clients and servers. */ + +void silc_server_new_id(SilcServer server, SilcSocketConnection sock, + SilcPacketContext *packet) +{ + silc_server_new_id_real(server, sock, packet, TRUE); +} + +/* Receoved New Id List packet, list of New ID payloads inside one + packet. Process the New ID payloads one by one. */ + +void silc_server_new_id_list(SilcServer server, SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcPacketContext *new_id; + SilcBuffer idp; + uint16 id_len; + + SILC_LOG_DEBUG(("Processing New ID List")); + + if (sock->type == SILC_SOCKET_TYPE_CLIENT || + packet->src_id_type != SILC_ID_SERVER) + return; + + /* If the sender of this packet is server and we are router we need to + broadcast this packet to other routers in the network. Broadcast + this list packet instead of multiple New ID packets. */ + if (!server->standalone && server->server_type == SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_SERVER && + !(packet->flags & SILC_PACKET_FLAG_BROADCAST)) { + SILC_LOG_DEBUG(("Broadcasting received New ID List packet")); + silc_server_packet_send(server, server->router->connection, + packet->type, + packet->flags | SILC_PACKET_FLAG_BROADCAST, + packet->buffer->data, packet->buffer->len, FALSE); + silc_server_backup_send(server, (SilcServerEntry)sock->user_data, + packet->type, packet->flags, + packet->buffer->data, packet->buffer->len, + FALSE, TRUE); + } + + /* Make copy of the original packet context, except for the actual + data buffer, which we will here now fetch from the original buffer. */ + new_id = silc_packet_context_alloc(); + new_id->type = SILC_PACKET_NEW_ID; + new_id->flags = packet->flags; + new_id->src_id = packet->src_id; + new_id->src_id_len = packet->src_id_len; + new_id->src_id_type = packet->src_id_type; + new_id->dst_id = packet->dst_id; + new_id->dst_id_len = packet->dst_id_len; + new_id->dst_id_type = packet->dst_id_type; + + idp = silc_buffer_alloc(256); + new_id->buffer = idp; + + while (packet->buffer->len) { + SILC_GET16_MSB(id_len, packet->buffer->data + 2); + if ((id_len > packet->buffer->len) || + (id_len > idp->truelen)) + break; + + silc_buffer_pull_tail(idp, 4 + id_len); + silc_buffer_put(idp, packet->buffer->data, 4 + id_len); + + /* Process the New ID */ + silc_server_new_id_real(server, sock, new_id, FALSE); + + silc_buffer_push_tail(idp, 4 + id_len); + silc_buffer_pull(packet->buffer, 4 + id_len); + } + + silc_buffer_free(idp); + silc_free(new_id); +} + +/* Received New Channel packet. Information about new channels in the + network are distributed using this packet. Save the information about + the new channel. This usually comes from router but also normal server + can send this to notify channels it has when it connects to us. */ + +void silc_server_new_channel(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcChannelPayload payload; + SilcChannelID *channel_id; + char *channel_name; + uint32 name_len; + unsigned char *id; + uint32 id_len; + uint32 mode; + SilcServerEntry server_entry; + SilcChannelEntry channel; + + SILC_LOG_DEBUG(("Processing New Channel")); + + if (sock->type == SILC_SOCKET_TYPE_CLIENT || + packet->src_id_type != SILC_ID_SERVER || + server->server_type == SILC_SERVER) + return; + + /* Parse the channel payload */ + payload = silc_channel_payload_parse(packet->buffer); + if (!payload) + return; + + /* Get the channel ID */ + channel_id = silc_channel_get_id_parse(payload); + if (!channel_id) { + silc_channel_payload_free(payload); + return; + } + + channel_name = silc_channel_get_name(payload, &name_len); + if (name_len > 256) + channel_name[255] = '\0'; + + id = silc_channel_get_id(payload, &id_len); + + server_entry = (SilcServerEntry)sock->user_data; + + if (sock->type == SILC_SOCKET_TYPE_ROUTER) { + /* Add the channel to global list as it is coming from router. It + cannot be our own channel as it is coming from router. */ + + /* Check that we don't already have this channel */ + channel = silc_idlist_find_channel_by_name(server->local_list, + channel_name, NULL); + if (!channel) + channel = silc_idlist_find_channel_by_name(server->global_list, + channel_name, NULL); + if (!channel) { + SILC_LOG_DEBUG(("New channel id(%s) from [Router] %s", + silc_id_render(channel_id, SILC_ID_CHANNEL), + sock->hostname)); + + silc_idlist_add_channel(server->global_list, strdup(channel_name), + 0, channel_id, sock->user_data, NULL, NULL); + server->stat.channels++; + } + } else { + /* The channel is coming from our server, thus it is in our cell + we will add it to our local list. */ + SilcBuffer chk; + + SILC_LOG_DEBUG(("Channel id(%s) from [Server] %s", + silc_id_render(channel_id, SILC_ID_CHANNEL), + sock->hostname)); + + /* Check that we don't already have this channel */ + channel = silc_idlist_find_channel_by_name(server->local_list, + channel_name, NULL); + if (!channel) + channel = silc_idlist_find_channel_by_name(server->global_list, + channel_name, NULL); + + /* If the channel does not exist, then create it. This creates a new + key to the channel as well that we will send to the server. */ + if (!channel) { + /* The protocol says that the Channel ID's IP address must be based + on the router's IP address. Check whether the ID is based in our + IP and if it is not then create a new ID and enforce the server + to switch the ID. */ + if (server_entry->server_type != SILC_BACKUP_ROUTER && + !SILC_ID_COMPARE(channel_id, server->id, server->id->ip.data_len)) { + SilcChannelID *tmp; + SILC_LOG_DEBUG(("Forcing the server to change Channel ID")); + + if (silc_id_create_channel_id(server, server->id, server->rng, &tmp)) { + silc_server_send_notify_channel_change(server, sock, FALSE, + channel_id, tmp); + silc_free(channel_id); + channel_id = tmp; + } + } + + /* Create the channel with the provided Channel ID */ + channel = silc_server_create_new_channel_with_id(server, NULL, NULL, + channel_name, + channel_id, FALSE); + if (!channel) { + silc_channel_payload_free(payload); + silc_free(channel_id); + return; + } + + /* Get the mode and set it to the channel */ + channel->mode = silc_channel_get_mode(payload); + + /* Send the new channel key to the server */ + chk = silc_channel_key_payload_encode(id_len, id, + strlen(channel->channel_key-> + cipher->name), + channel->channel_key->cipher->name, + channel->key_len / 8, + channel->key); + silc_server_packet_send(server, sock, SILC_PACKET_CHANNEL_KEY, 0, + chk->data, chk->len, FALSE); + silc_buffer_free(chk); + + } else { + /* The channel exist by that name, check whether the ID's match. + If they don't then we'll force the server to use the ID we have. + We also create a new key for the channel. */ + SilcBuffer users = NULL, users_modes = NULL; + + if (!SILC_ID_CHANNEL_COMPARE(channel_id, channel->id)) { + /* They don't match, send CHANNEL_CHANGE notify to the server to + force the ID change. */ + SILC_LOG_DEBUG(("Forcing the server to change Channel ID")); + silc_server_send_notify_channel_change(server, sock, FALSE, + channel_id, channel->id); + } + + /* If the mode is different from what we have then enforce the + mode change. */ + mode = silc_channel_get_mode(payload); + if (channel->mode != mode) { + SILC_LOG_DEBUG(("Forcing the server to change channel mode")); + silc_server_send_notify_cmode(server, sock, FALSE, channel, + channel->mode, server->id, + SILC_ID_SERVER, + channel->cipher, channel->hmac_name); + } + + /* Create new key for the channel and send it to the server and + everybody else possibly on the channel. */ + + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + if (!silc_server_create_channel_key(server, channel, 0)) + return; + + /* Send to the channel */ + silc_server_send_channel_key(server, sock, channel, FALSE); + id = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + id_len = SILC_ID_CHANNEL_LEN; + + /* Send to the server */ + chk = silc_channel_key_payload_encode(id_len, id, + strlen(channel->channel_key-> + cipher->name), + channel->channel_key-> + cipher->name, + channel->key_len / 8, + channel->key); + silc_server_packet_send(server, sock, SILC_PACKET_CHANNEL_KEY, 0, + chk->data, chk->len, FALSE); + silc_buffer_free(chk); + silc_free(id); + } + + silc_free(channel_id); + + /* Since the channel is coming from server and we also know about it + then send the JOIN notify to the server so that it see's our + users on the channel "joining" the channel. */ + silc_server_announce_get_channel_users(server, channel, &users, + &users_modes); + if (users) { + silc_buffer_push(users, users->data - users->head); + silc_server_packet_send(server, sock, + SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST, + users->data, users->len, FALSE); + silc_buffer_free(users); + } + if (users_modes) { + silc_buffer_push(users_modes, users_modes->data - users_modes->head); + silc_server_packet_send_dest(server, sock, + SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST, + channel->id, SILC_ID_CHANNEL, + users_modes->data, + users_modes->len, FALSE); + silc_buffer_free(users_modes); + } + } + } + + silc_channel_payload_free(payload); +} + +/* Received New Channel List packet, list of New Channel List payloads inside + one packet. Process the New Channel payloads one by one. */ + +void silc_server_new_channel_list(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcPacketContext *new; + SilcBuffer buffer; + uint16 len1, len2; + + SILC_LOG_DEBUG(("Processing New Channel List")); + + if (sock->type == SILC_SOCKET_TYPE_CLIENT || + packet->src_id_type != SILC_ID_SERVER || + server->server_type == SILC_SERVER) + return; + + /* If the sender of this packet is server and we are router we need to + broadcast this packet to other routers in the network. Broadcast + this list packet instead of multiple New Channel packets. */ + if (!server->standalone && server->server_type == SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_SERVER && + !(packet->flags & SILC_PACKET_FLAG_BROADCAST)) { + SILC_LOG_DEBUG(("Broadcasting received New Channel List packet")); + silc_server_packet_send(server, server->router->connection, + packet->type, + packet->flags | SILC_PACKET_FLAG_BROADCAST, + packet->buffer->data, packet->buffer->len, FALSE); + silc_server_backup_send(server, (SilcServerEntry)sock->user_data, + packet->type, packet->flags, + packet->buffer->data, packet->buffer->len, + FALSE, TRUE); + } + + /* Make copy of the original packet context, except for the actual + data buffer, which we will here now fetch from the original buffer. */ + new = silc_packet_context_alloc(); + new->type = SILC_PACKET_NEW_CHANNEL; + new->flags = packet->flags; + new->src_id = packet->src_id; + new->src_id_len = packet->src_id_len; + new->src_id_type = packet->src_id_type; + new->dst_id = packet->dst_id; + new->dst_id_len = packet->dst_id_len; + new->dst_id_type = packet->dst_id_type; + + buffer = silc_buffer_alloc(512); + new->buffer = buffer; + + while (packet->buffer->len) { + SILC_GET16_MSB(len1, packet->buffer->data); + if ((len1 > packet->buffer->len) || + (len1 > buffer->truelen)) + break; + + SILC_GET16_MSB(len2, packet->buffer->data + 2 + len1); + if ((len2 > packet->buffer->len) || + (len2 > buffer->truelen)) + break; + + silc_buffer_pull_tail(buffer, 8 + len1 + len2); + silc_buffer_put(buffer, packet->buffer->data, 8 + len1 + len2); + + /* Process the New Channel */ + silc_server_new_channel(server, sock, new); + + silc_buffer_push_tail(buffer, 8 + len1 + len2); + silc_buffer_pull(packet->buffer, 8 + len1 + len2); + } + + silc_buffer_free(buffer); + silc_free(new); +} + +/* Received key agreement packet. This packet is never for us. It is to + the client in the packet's destination ID. Sending of this sort of packet + equals sending private message, ie. it is sent point to point from + one client to another. */ + +void silc_server_key_agreement(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcSocketConnection dst_sock; + SilcIDListData idata; + + SILC_LOG_DEBUG(("Start")); + + if (packet->src_id_type != SILC_ID_CLIENT || + packet->dst_id_type != SILC_ID_CLIENT) + return; + + if (!packet->dst_id) + return; + + /* Get the route to the client */ + dst_sock = silc_server_get_client_route(server, packet->dst_id, + packet->dst_id_len, NULL, &idata); + if (!dst_sock) + return; + + /* Relay the packet */ + silc_server_relay_packet(server, dst_sock, idata->send_key, + idata->hmac_send, idata->psn_send++, + packet, FALSE); +} + +/* Received connection auth request packet that is used during connection + phase to resolve the mandatory authentication method. This packet can + actually be received at anytime but usually it is used only during + the connection authentication phase. Now, protocol says that this packet + can come from client or server, however, we support only this coming + from client and expect that server always knows what authentication + method to use. */ + +void silc_server_connection_auth_request(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcServerConfigSectionClientConnection *client = NULL; + uint16 conn_type; + int ret, port; + SilcAuthMethod auth_meth; + + SILC_LOG_DEBUG(("Start")); + + if (packet->src_id_type && packet->src_id_type != SILC_ID_CLIENT) + return; + + /* Parse the payload */ + ret = silc_buffer_unformat(packet->buffer, + SILC_STR_UI_SHORT(&conn_type), + SILC_STR_UI_SHORT(NULL), + SILC_STR_END); + if (ret == -1) + return; + + if (conn_type != SILC_SOCKET_TYPE_CLIENT) + return; + + /* Get the authentication method for the client */ + auth_meth = SILC_AUTH_NONE; + port = server->sockets[server->sock]->port; /* Listenning port */ + client = silc_server_config_find_client_conn(server->config, + sock->ip, + port); + if (!client) + client = silc_server_config_find_client_conn(server->config, + sock->hostname, + port); + if (client) + auth_meth = client->auth_meth; + + /* Send it back to the client */ + silc_server_send_connection_auth_request(server, sock, + conn_type, + auth_meth); +} + +/* Received REKEY packet. The sender of the packet wants to regenerate + its session keys. This starts the REKEY protocol. */ + +void silc_server_rekey(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcProtocol protocol; + SilcServerRekeyInternalContext *proto_ctx; + SilcIDListData idata = (SilcIDListData)sock->user_data; + + SILC_LOG_DEBUG(("Start")); + + /* Allocate internal protocol context. This is sent as context + to the protocol. */ + proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); + proto_ctx->server = (void *)server; + proto_ctx->sock = sock; + proto_ctx->responder = TRUE; + proto_ctx->pfs = idata->rekey->pfs; + + /* Perform rekey protocol. Will call the final callback after the + protocol is over. */ + silc_protocol_alloc(SILC_PROTOCOL_SERVER_REKEY, + &protocol, proto_ctx, silc_server_rekey_final); + sock->protocol = protocol; + + if (proto_ctx->pfs == FALSE) + /* Run the protocol */ + silc_protocol_execute(protocol, server->schedule, 0, 0); +} + +/* Received file transger packet. This packet is never for us. It is to + the client in the packet's destination ID. Sending of this sort of packet + equals sending private message, ie. it is sent point to point from + one client to another. */ + +void silc_server_ftp(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcSocketConnection dst_sock; + SilcIDListData idata; + + SILC_LOG_DEBUG(("Start")); + + if (packet->src_id_type != SILC_ID_CLIENT || + packet->dst_id_type != SILC_ID_CLIENT) + return; + + if (!packet->dst_id) + return; + + /* Get the route to the client */ + dst_sock = silc_server_get_client_route(server, packet->dst_id, + packet->dst_id_len, NULL, &idata); + if (!dst_sock) + return; + + /* Relay the packet */ + silc_server_relay_packet(server, dst_sock, idata->send_key, + idata->hmac_send, idata->psn_send++, + packet, FALSE); +} + diff --git a/apps/silcd/packet_receive.h b/apps/silcd/packet_receive.h new file mode 100644 index 00000000..847c131f --- /dev/null +++ b/apps/silcd/packet_receive.h @@ -0,0 +1,82 @@ +/* + + packet_receive.h + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2001 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. + +*/ + +#ifndef PACKET_RECEIVE_H +#define PACKET_RECEIVE_H + +/* Prototypes */ + +void silc_server_notify(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_notify_list(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_private_message(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_private_message_key(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_command_reply(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_channel_message(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_channel_key(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +SilcClientEntry silc_server_new_client(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +SilcServerEntry silc_server_new_server(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_new_channel(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_new_channel_list(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_new_id(SilcServer server, SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_new_id_list(SilcServer server, SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_remove_id(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_remove_id_list(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_key_agreement(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_connection_auth_request(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_rekey(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_ftp(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); + +#endif diff --git a/apps/silcd/packet_send.c b/apps/silcd/packet_send.c new file mode 100644 index 00000000..acea2644 --- /dev/null +++ b/apps/silcd/packet_send.c @@ -0,0 +1,1820 @@ +/* + + packet_send.c + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2001 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. + +*/ +/* + * Server packet routines to send packets. + */ +/* $Id$ */ + +#include "serverincludes.h" +#include "server_internal.h" + +/* 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 error. */ + +int silc_server_packet_send_real(SilcServer server, + SilcSocketConnection sock, + bool force_send) +{ + int ret; + + /* If disconnecting, ignore the data */ + if (SILC_IS_DISCONNECTING(sock)) + return -1; + + /* If rekey protocol is active we must assure that all packets are + sent through packet queue. */ + if (SILC_SERVER_IS_REKEY(sock)) + force_send = FALSE; + + /* If outbound data is already pending do not force send */ + if (SILC_IS_OUTBUF_PENDING(sock)) + force_send = FALSE; + + /* 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_server_packet_process. */ + SILC_SET_CONNECTION_FOR_OUTPUT(server->schedule, 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; +} + +/* Assembles a new packet to be sent out to network. This doesn't actually + send the packet but creates the packet and fills the outgoing data + buffer and marks the packet ready to be sent to network. However, If + argument force_send is TRUE the packet is sent immediately and not put + to queue. Normal case is that the packet is not sent immediately. */ + +void silc_server_packet_send(SilcServer server, + SilcSocketConnection sock, + SilcPacketType type, + SilcPacketFlags flags, + unsigned char *data, + uint32 data_len, + bool force_send) +{ + void *dst_id = NULL; + SilcIdType dst_id_type = SILC_ID_NONE; + SilcIDListData idata = (SilcIDListData)sock->user_data; + + if (!sock) + return; + + /* If disconnecting, ignore the data */ + if (SILC_IS_DISCONNECTING(sock)) + return; + + /* If entry is disabled do not sent anything. */ + if (idata && idata->status & SILC_IDLIST_STATUS_DISABLED) + return; + + /* Get data used in the packet sending, keys and stuff */ + switch(sock->type) { + case SILC_SOCKET_TYPE_CLIENT: + if (sock->user_data) { + dst_id = ((SilcClientEntry)sock->user_data)->id; + dst_id_type = SILC_ID_CLIENT; + } + break; + case SILC_SOCKET_TYPE_SERVER: + case SILC_SOCKET_TYPE_ROUTER: + if (sock->user_data) { + dst_id = ((SilcServerEntry)sock->user_data)->id; + dst_id_type = SILC_ID_SERVER; + } + break; + default: + break; + } + + silc_server_packet_send_dest(server, sock, type, flags, dst_id, + dst_id_type, data, data_len, force_send); +} + +/* Assembles a new packet to be sent out to network. This doesn't actually + send the packet but creates the packet and fills the outgoing data + buffer and marks the packet ready to be sent to network. However, If + argument force_send is TRUE the packet is sent immediately and not put + to queue. Normal case is that the packet is not sent immediately. + Destination information is sent as argument for this function. */ + +void silc_server_packet_send_dest(SilcServer server, + SilcSocketConnection sock, + SilcPacketType type, + SilcPacketFlags flags, + void *dst_id, + SilcIdType dst_id_type, + unsigned char *data, + uint32 data_len, + bool force_send) +{ + SilcPacketContext packetdata; + SilcIDListData idata = (SilcIDListData)sock->user_data; + SilcCipher cipher = NULL; + SilcHmac hmac = NULL; + uint32 sequence = 0; + unsigned char *dst_id_data = NULL; + uint32 dst_id_len = 0; + int block_len = 0; + + /* If disconnecting, ignore the data */ + if (SILC_IS_DISCONNECTING(sock)) + return; + + /* If entry is disabled do not sent anything. */ + if (idata && idata->status & SILC_IDLIST_STATUS_DISABLED) + return; + + SILC_LOG_DEBUG(("Sending packet, type %d", type)); + + if (dst_id) { + dst_id_data = silc_id_id2str(dst_id, dst_id_type); + dst_id_len = silc_id_get_len(dst_id, dst_id_type); + } + + if (idata) { + cipher = idata->send_key; + hmac = idata->hmac_send; + sequence = idata->psn_send++; + block_len = silc_cipher_get_block_len(cipher); + } + + /* Set the packet context pointers */ + packetdata.type = type; + packetdata.flags = flags; + packetdata.src_id = silc_id_id2str(server->id, server->id_type); + packetdata.src_id_len = silc_id_get_len(server->id, server->id_type); + packetdata.src_id_type = server->id_type; + packetdata.dst_id = dst_id_data; + packetdata.dst_id_len = dst_id_len; + packetdata.dst_id_type = dst_id_type; + packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + dst_id_len; + packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen, block_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, + 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, cipher); + + /* Encrypt the packet */ + silc_packet_encrypt(cipher, hmac, sequence, sock->outbuf, sock->outbuf->len); + + SILC_LOG_HEXDUMP(("Outgoing packet (%d), len %d", sequence, + sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_server_packet_send_real(server, sock, force_send); + + if (packetdata.src_id) + silc_free(packetdata.src_id); + if (packetdata.dst_id) + silc_free(packetdata.dst_id); +} + +/* Assembles a new packet to be sent out to network. This doesn't actually + send the packet but creates the packet and fills the outgoing data + buffer and marks the packet ready to be sent to network. However, If + argument force_send is TRUE the packet is sent immediately and not put + to queue. Normal case is that the packet is not sent immediately. + The source and destination information is sent as argument for this + function. */ + +void silc_server_packet_send_srcdest(SilcServer server, + SilcSocketConnection sock, + SilcPacketType type, + SilcPacketFlags flags, + void *src_id, + SilcIdType src_id_type, + void *dst_id, + SilcIdType dst_id_type, + unsigned char *data, + uint32 data_len, + bool force_send) +{ + SilcPacketContext packetdata; + SilcIDListData idata; + SilcCipher cipher = NULL; + SilcHmac hmac = NULL; + uint32 sequence = 0; + unsigned char *dst_id_data = NULL; + uint32 dst_id_len = 0; + unsigned char *src_id_data = NULL; + uint32 src_id_len = 0; + int block_len = 0; + + SILC_LOG_DEBUG(("Sending packet, type %d", type)); + + /* Get data used in the packet sending, keys and stuff */ + idata = (SilcIDListData)sock->user_data; + + if (dst_id) { + dst_id_data = silc_id_id2str(dst_id, dst_id_type); + dst_id_len = silc_id_get_len(dst_id, dst_id_type); + } + + if (src_id) { + src_id_data = silc_id_id2str(src_id, src_id_type); + src_id_len = silc_id_get_len(src_id, src_id_type); + } + + if (idata) { + cipher = idata->send_key; + hmac = idata->hmac_send; + sequence = idata->psn_send++; + block_len = silc_cipher_get_block_len(cipher); + } + + /* Set the packet context pointers */ + packetdata.type = type; + packetdata.flags = flags; + packetdata.src_id = src_id_data; + packetdata.src_id_len = src_id_len; + packetdata.src_id_type = src_id_type; + packetdata.dst_id = dst_id_data; + packetdata.dst_id_len = dst_id_len; + packetdata.dst_id_type = dst_id_type; + packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + + packetdata.src_id_len + dst_id_len; + packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen, block_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, + 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, cipher); + + /* Encrypt the packet */ + silc_packet_encrypt(cipher, hmac, sequence, sock->outbuf, sock->outbuf->len); + + SILC_LOG_HEXDUMP(("Outgoing packet (%d), len %d", sequence, + sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_server_packet_send_real(server, sock, force_send); + + if (packetdata.src_id) + silc_free(packetdata.src_id); + if (packetdata.dst_id) + silc_free(packetdata.dst_id); +} + +/* Broadcast received packet to our primary route. This function is used + by router to further route received broadcast packet. It is expected + that the broadcast flag from the packet is checked before calling this + function. This does not test or set the broadcast flag. */ + +void silc_server_packet_broadcast(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcIDListData idata; + void *id; + + SILC_LOG_DEBUG(("Broadcasting received broadcast packet")); + + /* If the packet is originated from our primary route we are + not allowed to send the packet. */ + id = silc_id_str2id(packet->src_id, packet->src_id_len, packet->src_id_type); + if (id && !SILC_ID_SERVER_COMPARE(id, server->router->id)) { + idata = (SilcIDListData)sock->user_data; + + silc_buffer_push(buffer, buffer->data - buffer->head); + silc_packet_send_prepare(sock, 0, 0, buffer->len); + silc_buffer_put(sock->outbuf, buffer->data, buffer->len); + silc_packet_encrypt(idata->send_key, idata->hmac_send, idata->psn_send++, + sock->outbuf, sock->outbuf->len); + + SILC_LOG_HEXDUMP(("Broadcasted packet (%d), len %d", idata->psn_send - 1, + sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_server_packet_send_real(server, sock, TRUE); + silc_free(id); + return; + } + + SILC_LOG_DEBUG(("Will not broadcast to primary route since it is the " + "original sender of this packet")); + silc_free(id); +} + +/* Routes received packet to `sock'. This is used to route the packets that + router receives but are not destined to it. */ + +void silc_server_packet_route(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + SilcIDListData idata; + + SILC_LOG_DEBUG(("Routing received packet")); + + idata = (SilcIDListData)sock->user_data; + + silc_buffer_push(buffer, buffer->data - buffer->head); + silc_packet_send_prepare(sock, 0, 0, buffer->len); + silc_buffer_put(sock->outbuf, buffer->data, buffer->len); + silc_packet_encrypt(idata->send_key, idata->hmac_send, idata->psn_send++, + sock->outbuf, sock->outbuf->len); + + SILC_LOG_HEXDUMP(("Routed packet (%d), len %d", idata->psn_send - 1, + sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_server_packet_send_real(server, sock, TRUE); +} + +/* This routine can be used to send a packet to table of clients provided + in `clients'. If `route' is FALSE the packet is routed only to local + clients (for server locally connected, and for router local cell). */ + +void silc_server_packet_send_clients(SilcServer server, + SilcClientEntry *clients, + uint32 clients_count, + SilcPacketType type, + SilcPacketFlags flags, + bool route, + unsigned char *data, + uint32 data_len, + bool force_send) +{ + SilcSocketConnection sock = NULL; + SilcClientEntry client = NULL; + SilcServerEntry *routed = NULL; + uint32 routed_count = 0; + bool gone = FALSE; + int i, k; + + SILC_LOG_DEBUG(("Sending packet to list of clients")); + + /* Send to all clients in table */ + for (i = 0; i < clients_count; i++) { + client = clients[i]; + + /* If client has router set it is not locally connected client and + we will route the message to the router set in the client. Though, + send locally connected server in all cases. */ + if (server->server_type == SILC_ROUTER && client->router && + ((!route && client->router->router == server->id_entry) || route)) { + + /* Check if we have sent the packet to this route already */ + for (k = 0; k < routed_count; k++) + if (routed[k] == client->router) + break; + if (k < routed_count) + continue; + + /* Route only once to router */ + sock = (SilcSocketConnection)client->router->connection; + if (sock->type == SILC_SOCKET_TYPE_ROUTER) { + if (gone) + continue; + gone = TRUE; + } + + /* Send the packet */ + silc_server_packet_send_dest(server, sock, type, flags, + client->router->id, SILC_ID_SERVER, + data, data_len, force_send); + + /* Mark this route routed already */ + routed = silc_realloc(routed, sizeof(*routed) * (routed_count + 1)); + routed[routed_count++] = client->router; + continue; + } + + if (client->router) + continue; + + /* Send to locally connected client */ + sock = (SilcSocketConnection)client->connection; + silc_server_packet_send_dest(server, sock, type, flags, + client->id, SILC_ID_CLIENT, + data, data_len, force_send); + } + + silc_free(routed); +} + +/* Internal routine to actually create the channel packet and send it + to network. This is common function in channel message sending. If + `channel_message' is TRUE this encrypts the message as it is strictly + a channel message. If FALSE normal encryption process is used. */ + +static void +silc_server_packet_send_to_channel_real(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet, + SilcCipher cipher, + SilcHmac hmac, + uint32 sequence, + unsigned char *data, + uint32 data_len, + bool channel_message, + bool force_send) +{ + int block_len; + packet->truelen = data_len + SILC_PACKET_HEADER_LEN + + packet->src_id_len + packet->dst_id_len; + + block_len = cipher ? silc_cipher_get_block_len(cipher) : 0; + if (channel_message) + packet->padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + + packet->src_id_len + + packet->dst_id_len), block_len); + else + packet->padlen = SILC_PACKET_PADLEN(packet->truelen, block_len); + + /* Prepare outgoing data buffer for packet sending */ + silc_packet_send_prepare(sock, + SILC_PACKET_HEADER_LEN + + packet->src_id_len + + packet->dst_id_len, + packet->padlen, + data_len); + + packet->buffer = sock->outbuf; + + /* Put the data to buffer, assemble and encrypt the packet. The packet + is encrypted with normal session key shared with the client, unless + the `channel_message' is TRUE. */ + silc_buffer_put(sock->outbuf, data, data_len); + silc_packet_assemble(packet, cipher); + if (channel_message) + silc_packet_encrypt(cipher, hmac, sequence, sock->outbuf, + SILC_PACKET_HEADER_LEN + packet->src_id_len + + packet->dst_id_len + packet->padlen); + else + silc_packet_encrypt(cipher, hmac, sequence, sock->outbuf, + sock->outbuf->len); + + SILC_LOG_HEXDUMP(("Channel packet (%d), len %d", sequence, + sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_server_packet_send_real(server, sock, force_send); +} + +/* This routine is used by the server to send packets to channel. The + packet sent with this function is distributed to all clients on + the channel. Usually this is used to send notify messages to the + channel, things like notify about new user joining to the channel. + If `route' is FALSE then the packet is sent only locally and will not + be routed anywhere (for router locally means cell wide). If `sender' + is provided then the packet is not sent to that connection since it + originally came from it. */ + +void silc_server_packet_send_to_channel(SilcServer server, + SilcSocketConnection sender, + SilcChannelEntry channel, + SilcPacketType type, + bool route, + unsigned char *data, + uint32 data_len, + bool force_send) +{ + SilcSocketConnection sock = NULL; + SilcPacketContext packetdata; + SilcClientEntry client = NULL; + SilcServerEntry *routed = NULL; + SilcChannelClientEntry chl; + SilcHashTableList htl; + SilcIDListData idata; + uint32 routed_count = 0; + bool gone = FALSE; + int k; + + /* This doesn't send channel message packets */ + assert(type != SILC_PACKET_CHANNEL_MESSAGE); + + SILC_LOG_DEBUG(("Sending packet to channel")); + + /* Set the packet context pointers. */ + packetdata.flags = 0; + packetdata.type = type; + packetdata.src_id = silc_id_id2str(server->id, SILC_ID_SERVER); + packetdata.src_id_len = silc_id_get_len(server->id, SILC_ID_SERVER); + packetdata.src_id_type = SILC_ID_SERVER; + packetdata.dst_id = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + packetdata.dst_id_len = silc_id_get_len(channel->id, SILC_ID_CHANNEL); + packetdata.dst_id_type = SILC_ID_CHANNEL; + + /* If there are global users in the channel we will send the message + first to our router for further routing. */ + if (route && server->server_type != SILC_ROUTER && !server->standalone && + channel->global_users) { + SilcServerEntry router; + + /* Get data used in packet header encryption, keys and stuff. */ + router = server->router; + sock = (SilcSocketConnection)router->connection; + idata = (SilcIDListData)router; + + if (sock != sender) { + SILC_LOG_DEBUG(("Sending channel message to router for routing")); + + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, FALSE, + force_send); + } + } + + routed = silc_calloc(silc_hash_table_count(channel->user_list), + sizeof(*routed)); + + /* Send the message to clients on the channel's client list. */ + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + client = chl->client; + if (!client) + continue; + + /* If client has router set it is not locally connected client and + we will route the message to the router set in the client. Though, + send locally connected server in all cases. */ + if (server->server_type == SILC_ROUTER && client->router && + ((!route && client->router->router == server->id_entry) || route)) { + + /* Check if we have sent the packet to this route already */ + for (k = 0; k < routed_count; k++) + if (routed[k] == client->router) + break; + if (k < routed_count) + continue; + + /* Get data used in packet header encryption, keys and stuff. */ + sock = (SilcSocketConnection)client->router->connection; + idata = (SilcIDListData)client->router; + + if (sender && sock == sender) + continue; + + /* Route only once to router */ + if (sock->type == SILC_SOCKET_TYPE_ROUTER) { + if (gone) + continue; + gone = TRUE; + } + + /* Send the packet */ + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, FALSE, + force_send); + + /* Mark this route routed already */ + routed[routed_count++] = client->router; + continue; + } + + if (client->router) + continue; + + /* Send to locally connected client */ + + /* Get data used in packet header encryption, keys and stuff. */ + sock = (SilcSocketConnection)client->connection; + idata = (SilcIDListData)client; + + if (sender && sock == sender) + continue; + + /* Send the packet */ + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, FALSE, + force_send); + } + + silc_free(routed); + silc_free(packetdata.src_id); + silc_free(packetdata.dst_id); +} + +/* This checks whether the relayed packet came from router. If it did + then we'll need to encrypt it with the channel key. This is called + from the silc_server_packet_relay_to_channel. */ + +static bool +silc_server_packet_relay_to_channel_encrypt(SilcServer server, + SilcSocketConnection sock, + SilcChannelEntry channel, + unsigned char *data, + unsigned int data_len) +{ + /* If we are router and the packet came from router and private key + has not been set for the channel then we must encrypt the packet + as it was decrypted with the session key shared between us and the + router which sent it. This is so, because cells does not share the + same channel key. */ + if (server->server_type == SILC_ROUTER && + sock->type == SILC_SOCKET_TYPE_ROUTER && + !(channel->mode & SILC_CHANNEL_MODE_PRIVKEY) && + channel->channel_key) { + SilcBuffer chp; + uint32 iv_len, i; + uint16 dlen, flags; + + iv_len = silc_cipher_get_block_len(channel->channel_key); + if (channel->iv[0] == '\0') + for (i = 0; i < iv_len; i++) channel->iv[i] = + silc_rng_get_byte(server->rng); + else + silc_hash_make(server->md5hash, channel->iv, iv_len, channel->iv); + + /* Encode new payload. This encrypts it also. */ + SILC_GET16_MSB(flags, data); + SILC_GET16_MSB(dlen, data + 2); + + if (dlen > data_len) { + SILC_LOG_WARNING(("Corrupted channel message, cannot relay it")); + return FALSE; + } + + chp = silc_channel_message_payload_encode(flags, dlen, data + 4, + iv_len, channel->iv, + channel->channel_key, + channel->hmac); + memcpy(data, chp->data, chp->len); + silc_buffer_free(chp); + } + + return TRUE; +} + +/* This routine is explicitly used to relay messages to some channel. + Packets sent with this function we have received earlier and are + totally encrypted. This just sends the packet to all clients on + the channel. If the sender of the packet is someone on the channel + the message will not be sent to that client. The SILC Packet header + is encrypted with the session key shared between us and the client. + MAC is also computed before encrypting the header. Rest of the + packet will be untouched. */ + +void silc_server_packet_relay_to_channel(SilcServer server, + SilcSocketConnection sender_sock, + SilcChannelEntry channel, + void *sender, + SilcIdType sender_type, + void *sender_entry, + unsigned char *data, + uint32 data_len, + bool force_send) +{ + bool found = FALSE; + SilcSocketConnection sock = NULL; + SilcPacketContext packetdata; + SilcClientEntry client = NULL; + SilcServerEntry *routed = NULL; + SilcChannelClientEntry chl; + uint32 routed_count = 0; + SilcIDListData idata; + SilcHashTableList htl; + bool gone = FALSE; + int k; + + SILC_LOG_DEBUG(("Relaying packet to channel")); + + /* This encrypts the packet, if needed. It will be encrypted if + it came from the router thus it needs to be encrypted with the + channel key. If the channel key does not exist, then we know we + don't have a single local user on the channel. */ + if (!silc_server_packet_relay_to_channel_encrypt(server, sender_sock, + channel, data, + data_len)) + return; + + /* Set the packet context pointers. */ + packetdata.flags = 0; + packetdata.type = SILC_PACKET_CHANNEL_MESSAGE; + packetdata.src_id = silc_id_id2str(sender, sender_type); + packetdata.src_id_len = silc_id_get_len(sender, sender_type); + packetdata.src_id_type = sender_type; + packetdata.dst_id = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + packetdata.dst_id_len = silc_id_get_len(channel->id, SILC_ID_CHANNEL); + packetdata.dst_id_type = SILC_ID_CHANNEL; + + /* If there are global users in the channel we will send the message + first to our router for further routing. */ + if (server->server_type != SILC_ROUTER && !server->standalone && + channel->global_users) { + SilcServerEntry router = server->router; + + /* Check that the sender is not our router. */ + if (sender_sock != (SilcSocketConnection)router->connection) { + + /* Get data used in packet header encryption, keys and stuff. */ + sock = (SilcSocketConnection)router->connection; + idata = (SilcIDListData)router; + + SILC_LOG_DEBUG(("Sending channel message to router for routing")); + + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, TRUE, + force_send); + } + } + + routed = silc_calloc(silc_hash_table_count(channel->user_list), + sizeof(*routed)); + + /* Mark that to the route the original sender if from is not routed */ + if (sender_type == SILC_ID_CLIENT) { + client = (SilcClientEntry)sender_entry; + if (client->router) { + routed[routed_count++] = client->router; + SILC_LOG_DEBUG(("************* router %s", + silc_id_render(client->router->id, SILC_ID_SERVER))); + } + } + + /* Send the message to clients on the channel's client list. */ + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + client = chl->client; + if (!client) + continue; + + /* Do not send to the sender */ + if (!found && client == sender_entry) { + found = TRUE; + continue; + } + + /* If the client has set router it means that it is not locally + connected client and we will route the packet further. */ + if (server->server_type == SILC_ROUTER && client->router) { + + /* Sender maybe server as well so we want to make sure that + we won't send the message to the server it came from. */ + if (!found && SILC_ID_SERVER_COMPARE(client->router->id, sender)) { + found = TRUE; + routed[routed_count++] = client->router; + continue; + } + + /* Check if we have sent the packet to this route already */ + for (k = 0; k < routed_count; k++) + if (routed[k] == client->router) + break; + if (k < routed_count) + continue; + + /* Get data used in packet header encryption, keys and stuff. */ + sock = (SilcSocketConnection)client->router->connection; + idata = (SilcIDListData)client->router; + + /* Do not send to the sender. Check first whether the true + sender's router is same as this client's router. Also check + if the sender socket is the same as this client's router + socket. */ + if (sender_entry && + ((SilcClientEntry)sender_entry)->router == client->router) + continue; + if (sender_sock && sock == sender_sock) + continue; + + SILC_LOG_DEBUG(("Relaying packet to client ID(%s) %s (%s)", + silc_id_render(client->id, SILC_ID_CLIENT), + sock->hostname, sock->ip)); + + /* Mark this route routed already. */ + routed[routed_count++] = client->router; + + /* If the remote connection is router then we'll decrypt the + channel message and re-encrypt it with the session key shared + between us and the remote router. This is done because the + channel keys are cell specific and we have different channel + key than the remote router has. */ + if (sock->type == SILC_SOCKET_TYPE_ROUTER) { + if (gone) + continue; + + SILC_LOG_DEBUG(("Remote is router, encrypt with session key")); + gone = TRUE; + + /* If private key mode is not set then decrypt the packet + and re-encrypt it */ + if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + unsigned char *tmp = silc_calloc(data_len, sizeof(*data)); + memcpy(tmp, data, data_len); + + /* Decrypt the channel message (we don't check the MAC) */ + if (channel->channel_key && + !silc_channel_message_payload_decrypt(tmp, data_len, + channel->channel_key, + NULL)) { + memset(tmp, 0, data_len); + silc_free(tmp); + continue; + } + + /* Now re-encrypt and send it to the router */ + silc_server_packet_send_srcdest(server, sock, + SILC_PACKET_CHANNEL_MESSAGE, 0, + sender, sender_type, + channel->id, SILC_ID_CHANNEL, + tmp, data_len, force_send); + + /* Free the copy of the channel message */ + memset(tmp, 0, data_len); + silc_free(tmp); + } else { + /* Private key mode is set, we don't have the channel key, so + just re-encrypt the entire packet and send it to the router. */ + silc_server_packet_send_srcdest(server, sock, + SILC_PACKET_CHANNEL_MESSAGE, 0, + sender, sender_type, + channel->id, SILC_ID_CHANNEL, + data, data_len, force_send); + } + continue; + } + + /* Send the packet (to normal server) */ + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, TRUE, + force_send); + + continue; + } + + if (client->router) + continue; + + /* Get data used in packet header encryption, keys and stuff. */ + sock = (SilcSocketConnection)client->connection; + idata = (SilcIDListData)client; + + if (sender_sock && sock == sender_sock) + continue; + + SILC_LOG_DEBUG(("Sending packet to client ID(%s) %s (%s)", + silc_id_render(client->id, SILC_ID_CLIENT), + sock->hostname, sock->ip)); + + /* Send the packet */ + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, TRUE, + force_send); + } + + silc_free(routed); + silc_free(packetdata.src_id); + silc_free(packetdata.dst_id); +} + +/* This function is used to send packets strictly to all local clients + on a particular channel. This is used for example to distribute new + channel key to all our locally connected clients on the channel. + The packets are always encrypted with the session key shared between + the client, this means these are not _to the channel_ but _to the client_ + on the channel. */ + +void silc_server_packet_send_local_channel(SilcServer server, + SilcChannelEntry channel, + SilcPacketType type, + SilcPacketFlags flags, + unsigned char *data, + uint32 data_len, + bool force_send) +{ + SilcChannelClientEntry chl; + SilcHashTableList htl; + SilcSocketConnection sock = NULL; + + SILC_LOG_DEBUG(("Start")); + + /* Send the message to clients on the channel's client list. */ + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + if (chl->client && !chl->client->router) { + sock = (SilcSocketConnection)chl->client->connection; + + /* Send the packet to the client */ + silc_server_packet_send_dest(server, sock, type, flags, chl->client->id, + SILC_ID_CLIENT, data, data_len, + force_send); + } + } +} + +/* Routine used to send (relay, route) private messages to some destination. + If the private message key does not exist then the message is re-encrypted, + otherwise we just pass it along. This really is not used to send new + private messages (as server does not send them) but to relay received + private messages. */ + +void silc_server_send_private_message(SilcServer server, + SilcSocketConnection dst_sock, + SilcCipher cipher, + SilcHmac hmac, + uint32 sequence, + SilcPacketContext *packet) +{ + SilcBuffer buffer = packet->buffer; + + /* Re-encrypt and send if private messge key does not exist */ + if (!(packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY)) { + + silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len + + packet->dst_id_len + packet->padlen); + silc_packet_send_prepare(dst_sock, 0, 0, buffer->len); + silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); + + /* Re-encrypt packet */ + silc_packet_encrypt(cipher, hmac, sequence, dst_sock->outbuf, buffer->len); + + /* Send the packet */ + silc_server_packet_send_real(server, dst_sock, FALSE); + + } else { + /* Key exist so encrypt just header and send it */ + silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len + + packet->dst_id_len + packet->padlen); + silc_packet_send_prepare(dst_sock, 0, 0, buffer->len); + silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); + + /* Encrypt header */ + silc_packet_encrypt(cipher, hmac, sequence, dst_sock->outbuf, + SILC_PACKET_HEADER_LEN + packet->src_id_len + + packet->dst_id_len + packet->padlen); + + silc_server_packet_send_real(server, dst_sock, FALSE); + } +} + +/* Sends current motd to client */ + +void silc_server_send_motd(SilcServer server, + SilcSocketConnection sock) +{ + char *motd; + uint32 motd_len; + + if (server->config && server->config->motd && + server->config->motd->motd_file) { + + motd = silc_file_readfile(server->config->motd->motd_file, &motd_len); + if (!motd) + return; + + silc_server_send_notify(server, sock, FALSE, SILC_NOTIFY_TYPE_MOTD, 1, + motd, motd_len); + silc_free(motd); + } +} + +/* Sends error message. Error messages may or may not have any + implications. */ + +void silc_server_send_error(SilcServer server, + SilcSocketConnection sock, + const char *fmt, ...) +{ + va_list ap; + unsigned char buf[4096]; + + memset(buf, 0, sizeof(buf)); + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + + silc_server_packet_send(server, sock, SILC_PACKET_ERROR, 0, + buf, strlen(buf), FALSE); +} + +/* Sends notify message. If format is TRUE the variable arguments are + formatted and the formatted string is sent as argument payload. If it is + FALSE then each argument is sent as separate argument and their format + in the argument list must be { argument data, argument length }. */ + +void silc_server_send_notify(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcNotifyType type, + uint32 argc, ...) +{ + va_list ap; + SilcBuffer packet; + + va_start(ap, argc); + + packet = silc_notify_payload_encode(type, argc, ap); + silc_server_packet_send(server, sock, SILC_PACKET_NOTIFY, + broadcast ? SILC_PACKET_FLAG_BROADCAST : 0, + packet->data, packet->len, FALSE); + + /* Send to backup routers if this is being broadcasted to primary + router. */ + if (server->router && server->router->connection && + sock == server->router->connection && broadcast) + silc_server_backup_send(server, NULL, SILC_PACKET_NOTIFY, 0, + packet->data, packet->len, FALSE, TRUE); + + silc_buffer_free(packet); + va_end(ap); +} + +/* Sends notify message and gets the arguments from the `args' Argument + Payloads. */ + +void silc_server_send_notify_args(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcNotifyType type, + uint32 argc, + SilcBuffer args) +{ + SilcBuffer packet; + + packet = silc_notify_payload_encode_args(type, argc, args); + silc_server_packet_send(server, sock, SILC_PACKET_NOTIFY, + broadcast ? SILC_PACKET_FLAG_BROADCAST : 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); +} + +/* Send CHANNEL_CHANGE notify type. This tells the receiver to replace the + `old_id' with the `new_id'. */ + +void silc_server_send_notify_channel_change(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelID *old_id, + SilcChannelID *new_id) +{ + SilcBuffer idp1, idp2; + + idp1 = silc_id_payload_encode((void *)old_id, SILC_ID_CHANNEL); + idp2 = silc_id_payload_encode((void *)new_id, SILC_ID_CHANNEL); + + silc_server_send_notify(server, sock, broadcast, + SILC_NOTIFY_TYPE_CHANNEL_CHANGE, + 2, idp1->data, idp1->len, idp2->data, idp2->len); + silc_buffer_free(idp1); + silc_buffer_free(idp2); +} + +/* Send NICK_CHANGE notify type. This tells the receiver to replace the + `old_id' with the `new_id'. */ + +void silc_server_send_notify_nick_change(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *old_id, + SilcClientID *new_id) +{ + SilcBuffer idp1, idp2; + + idp1 = silc_id_payload_encode((void *)old_id, SILC_ID_CLIENT); + idp2 = silc_id_payload_encode((void *)new_id, SILC_ID_CLIENT); + + silc_server_send_notify(server, sock, broadcast, + SILC_NOTIFY_TYPE_NICK_CHANGE, + 2, idp1->data, idp1->len, idp2->data, idp2->len); + silc_buffer_free(idp1); + silc_buffer_free(idp2); +} + +/* Sends JOIN notify type. This tells that new client by `client_id' ID + has joined to the `channel'. */ + +void silc_server_send_notify_join(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id) +{ + SilcBuffer idp1, idp2; + + idp1 = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + idp2 = silc_id_payload_encode((void *)channel->id, SILC_ID_CHANNEL); + silc_server_send_notify(server, sock, broadcast, SILC_NOTIFY_TYPE_JOIN, + 2, idp1->data, idp1->len, + idp2->data, idp2->len); + silc_buffer_free(idp1); + silc_buffer_free(idp2); +} + +/* Sends LEAVE notify type. This tells that `client_id' has left the + `channel'. The Notify packet is always destined to the channel. */ + +void silc_server_send_notify_leave(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id) +{ + SilcBuffer idp; + + idp = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + silc_server_send_notify_dest(server, sock, broadcast, (void *)channel->id, + SILC_ID_CHANNEL, SILC_NOTIFY_TYPE_LEAVE, + 1, idp->data, idp->len); + silc_buffer_free(idp); +} + +/* Sends CMODE_CHANGE notify type. This tells that `client_id' changed the + `channel' mode to `mode. The Notify packet is always destined to + the channel. */ + +void silc_server_send_notify_cmode(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + uint32 mode_mask, + void *id, SilcIdType id_type, + char *cipher, char *hmac) +{ + SilcBuffer idp; + unsigned char mode[4]; + + idp = silc_id_payload_encode((void *)id, id_type); + SILC_PUT32_MSB(mode_mask, mode); + + silc_server_send_notify_dest(server, sock, broadcast, (void *)channel->id, + SILC_ID_CHANNEL, SILC_NOTIFY_TYPE_CMODE_CHANGE, + 4, idp->data, idp->len, + mode, 4, + cipher, cipher ? strlen(cipher) : 0, + hmac, hmac ? strlen(hmac) : 0); + silc_buffer_free(idp); +} + +/* Sends CUMODE_CHANGE notify type. This tells that `client_id' changed the + `target' client's mode on `channel'. The Notify packet is always + destined to the channel. */ + +void silc_server_send_notify_cumode(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + uint32 mode_mask, + void *id, SilcIdType id_type, + SilcClientID *target) +{ + SilcBuffer idp1, idp2; + unsigned char mode[4]; + + idp1 = silc_id_payload_encode((void *)id, id_type); + idp2 = silc_id_payload_encode((void *)target, SILC_ID_CLIENT); + SILC_PUT32_MSB(mode_mask, mode); + + silc_server_send_notify_dest(server, sock, broadcast, (void *)channel->id, + SILC_ID_CHANNEL, + SILC_NOTIFY_TYPE_CUMODE_CHANGE, 3, + idp1->data, idp1->len, + mode, 4, + idp2->data, idp2->len); + silc_buffer_free(idp1); + silc_buffer_free(idp2); +} + +/* Sends SIGNOFF notify type. This tells that `client_id' client has + left SILC network. This function is used only between server and router + traffic. This is not used to send the notify to the channel for + client. The `message may be NULL. */ + +void silc_server_send_notify_signoff(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *client_id, + char *message) +{ + SilcBuffer idp; + + idp = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + silc_server_send_notify(server, sock, broadcast, + SILC_NOTIFY_TYPE_SIGNOFF, + message ? 2 : 1, idp->data, idp->len, + message, message ? strlen(message): 0); + silc_buffer_free(idp); +} + +/* Sends TOPIC_SET notify type. This tells that `client_id' changed + the `channel's topic to `topic'. The Notify packet is always destined + to the channel. This function is used to send the topic set notifies + between routers. */ + +void silc_server_send_notify_topic_set(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id, + char *topic) +{ + SilcBuffer idp; + + idp = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + silc_server_send_notify(server, sock, broadcast, + SILC_NOTIFY_TYPE_TOPIC_SET, + topic ? 2 : 1, + idp->data, idp->len, + topic, topic ? strlen(topic) : 0); + silc_buffer_free(idp); +} + +/* Send KICKED notify type. This tells that the `client_id' on `channel' + was kicked off the channel. The `comment' may indicate the reason + for the kicking. This function is used only between server and router + traffic. */ + +void silc_server_send_notify_kicked(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id, + char *comment) +{ + SilcBuffer idp; + + idp = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + silc_server_send_notify_dest(server, sock, broadcast, (void *)channel->id, + SILC_ID_CHANNEL, SILC_NOTIFY_TYPE_KICKED, + comment ? 2 : 1, idp->data, idp->len, + comment, comment ? strlen(comment) : 0); + silc_buffer_free(idp); +} + +/* Send KILLED notify type. This tells that the `client_id' client was + killed from the network. The `comment' may indicate the reason + for the killing. */ + +void silc_server_send_notify_killed(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *client_id, + char *comment) +{ + SilcBuffer idp; + + idp = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + silc_server_send_notify_dest(server, sock, broadcast, (void *)client_id, + SILC_ID_CLIENT, SILC_NOTIFY_TYPE_KILLED, + comment ? 2 : 1, idp->data, idp->len, + comment, comment ? strlen(comment) : 0); + silc_buffer_free(idp); +} + +/* Sends UMODE_CHANGE notify type. This tells that `client_id' client's + user mode in the SILC Network was changed. This function is used to + send the packet between routers as broadcast packet. */ + +void silc_server_send_notify_umode(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *client_id, + uint32 mode_mask) +{ + SilcBuffer idp; + unsigned char mode[4]; + + idp = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + SILC_PUT32_MSB(mode_mask, mode); + + silc_server_send_notify(server, sock, broadcast, + SILC_NOTIFY_TYPE_UMODE_CHANGE, 2, + idp->data, idp->len, + mode, 4); + silc_buffer_free(idp); +} + +/* Sends BAN notify type. This tells that ban has been either `add'ed + or `del'eted on the `channel. This function is used to send the packet + between routers as broadcast packet. */ + +void silc_server_send_notify_ban(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + char *add, char *del) +{ + SilcBuffer idp; + + idp = silc_id_payload_encode((void *)channel->id, SILC_ID_CHANNEL); + silc_server_send_notify(server, sock, broadcast, + SILC_NOTIFY_TYPE_BAN, 3, + idp->data, idp->len, + add, add ? strlen(add) : 0, + del, del ? strlen(del) : 0); + silc_buffer_free(idp); +} + +/* Sends INVITE notify type. This tells that invite has been either `add'ed + or `del'eted on the `channel. The sender of the invite is the `client_id'. + This function is used to send the packet between routers as broadcast + packet. */ + +void silc_server_send_notify_invite(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id, + char *add, char *del) +{ + SilcBuffer idp, idp2; + + idp = silc_id_payload_encode((void *)channel->id, SILC_ID_CHANNEL); + idp2 = silc_id_payload_encode((void *)client_id, SILC_ID_CLIENT); + silc_server_send_notify(server, sock, broadcast, + SILC_NOTIFY_TYPE_INVITE, 5, + idp->data, idp->len, + channel->channel_name, strlen(channel->channel_name), + idp2->data, idp2->len, + add, add ? strlen(add) : 0, + del, del ? strlen(del) : 0); + silc_buffer_free(idp); + silc_buffer_free(idp2); +} + +/* Sends notify message destined to specific entity. */ + +void silc_server_send_notify_dest(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + void *dest_id, + SilcIdType dest_id_type, + SilcNotifyType type, + uint32 argc, ...) +{ + va_list ap; + SilcBuffer packet; + + va_start(ap, argc); + + packet = silc_notify_payload_encode(type, argc, ap); + silc_server_packet_send_dest(server, sock, SILC_PACKET_NOTIFY, + broadcast ? SILC_PACKET_FLAG_BROADCAST : 0, + dest_id, dest_id_type, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + va_end(ap); +} + +/* Sends notify message to a channel. The notify message sent is + distributed to all clients on the channel. If `route_notify' is TRUE + then the notify may be routed to primary route or to some other routers. + If FALSE it is assured that the notify is sent only locally. If `sender' + is provided then the packet is not sent to that connection since it + originally came from it. */ + +void silc_server_send_notify_to_channel(SilcServer server, + SilcSocketConnection sender, + SilcChannelEntry channel, + unsigned char route_notify, + SilcNotifyType type, + uint32 argc, ...) +{ + va_list ap; + SilcBuffer packet; + + va_start(ap, argc); + + packet = silc_notify_payload_encode(type, argc, ap); + silc_server_packet_send_to_channel(server, sender, channel, + SILC_PACKET_NOTIFY, route_notify, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + va_end(ap); +} + +/* Send notify message to all channels the client has joined. It is quaranteed + that the message is sent only once to a client (ie. if a client is joined + on two same channel it will receive only one notify message). Also, this + sends only to local clients (locally connected if we are server, and to + local servers if we are router). If `sender' is provided the packet is + not sent to that client at all. */ + +void silc_server_send_notify_on_channels(SilcServer server, + SilcClientEntry sender, + SilcClientEntry client, + SilcNotifyType type, + uint32 argc, ...) +{ + int k; + SilcSocketConnection sock = NULL; + SilcPacketContext packetdata; + SilcClientEntry c; + SilcClientEntry *sent_clients = NULL; + uint32 sent_clients_count = 0; + SilcServerEntry *routed = NULL; + uint32 routed_count = 0; + SilcHashTableList htl, htl2; + SilcChannelEntry channel; + SilcChannelClientEntry chl, chl2; + SilcIDListData idata; + SilcBuffer packet; + unsigned char *data; + uint32 data_len; + bool force_send = FALSE; + va_list ap; + + SILC_LOG_DEBUG(("Start")); + + if (!silc_hash_table_count(client->channels)) + return; + + va_start(ap, argc); + packet = silc_notify_payload_encode(type, argc, ap); + data = packet->data; + data_len = packet->len; + + /* Set the packet context pointers. */ + packetdata.flags = 0; + packetdata.type = SILC_PACKET_NOTIFY; + packetdata.src_id = silc_id_id2str(server->id, SILC_ID_SERVER); + packetdata.src_id_len = silc_id_get_len(server->id, SILC_ID_SERVER); + packetdata.src_id_type = SILC_ID_SERVER; + + silc_hash_table_list(client->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + channel = chl->channel; + + /* Send the message to all clients on the channel's client list. */ + silc_hash_table_list(channel->user_list, &htl2); + while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) { + c = chl2->client; + + if (sender && c == sender) + continue; + + /* Check if we have sent the packet to this client already */ + for (k = 0; k < sent_clients_count; k++) + if (sent_clients[k] == c) + break; + if (k < sent_clients_count) + continue; + + /* If we are router and if this client has router set it is not + locally connected client and we will route the message to the + router set in the client. */ + if (c && c->router && server->server_type == SILC_ROUTER) { + /* Check if we have sent the packet to this route already */ + for (k = 0; k < routed_count; k++) + if (routed[k] == c->router) + break; + if (k < routed_count) + continue; + + /* Get data used in packet header encryption, keys and stuff. */ + sock = (SilcSocketConnection)c->router->connection; + idata = (SilcIDListData)c->router; + + { + SILC_LOG_DEBUG(("*****************")); + SILC_LOG_DEBUG(("client->router->id %s", + silc_id_render(c->router->id, SILC_ID_SERVER))); + SILC_LOG_DEBUG(("client->router->connection->user_data->id %s", + silc_id_render(((SilcServerEntry)sock->user_data)->id, SILC_ID_SERVER))); + } + + packetdata.dst_id = silc_id_id2str(c->router->id, SILC_ID_SERVER); + packetdata.dst_id_len = silc_id_get_len(c->router->id, SILC_ID_SERVER); + packetdata.dst_id_type = SILC_ID_SERVER; + + /* Send the packet */ + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, FALSE, + force_send); + + silc_free(packetdata.dst_id); + + /* We want to make sure that the packet is routed to same router + only once. Mark this route as sent route. */ + routed = silc_realloc(routed, sizeof(*routed) * (routed_count + 1)); + routed[routed_count++] = c->router; + continue; + } + + if (c && c->router) + continue; + + /* Send to locally connected client */ + if (c) { + + /* Get data used in packet header encryption, keys and stuff. */ + sock = (SilcSocketConnection)c->connection; + idata = (SilcIDListData)c; + + packetdata.dst_id = silc_id_id2str(c->id, SILC_ID_CLIENT); + packetdata.dst_id_len = silc_id_get_len(c->id, SILC_ID_CLIENT); + packetdata.dst_id_type = SILC_ID_CLIENT; + + /* Send the packet */ + silc_server_packet_send_to_channel_real(server, sock, &packetdata, + idata->send_key, + idata->hmac_send, + idata->psn_send++, + data, data_len, FALSE, + force_send); + + silc_free(packetdata.dst_id); + + /* Make sure that we send the notify only once per client. */ + sent_clients = silc_realloc(sent_clients, sizeof(*sent_clients) * + (sent_clients_count + 1)); + sent_clients[sent_clients_count++] = c; + } + } + } + + silc_free(routed); + silc_free(sent_clients); + silc_free(packetdata.src_id); + va_end(ap); +} + +/* Sends New ID Payload to remote end. The packet is used to distribute + information about new registered clients, servers, channel etc. usually + to routers so that they can keep these information up to date. + If the argument `broadcast' is TRUE then the packet is sent as + broadcast packet. */ + +void silc_server_send_new_id(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + void *id, SilcIdType id_type, + uint32 id_len) +{ + SilcBuffer idp; + + SILC_LOG_DEBUG(("Start")); + + idp = silc_id_payload_encode(id, id_type); + silc_server_packet_send(server, sock, SILC_PACKET_NEW_ID, + broadcast ? SILC_PACKET_FLAG_BROADCAST : 0, + idp->data, idp->len, FALSE); + + /* Send to backup routers if this is being broadcasted to primary + router. */ + if (server->router && server->router->connection && + sock == server->router->connection && broadcast) + silc_server_backup_send(server, NULL, SILC_PACKET_NEW_ID, 0, + idp->data, idp->len, FALSE, TRUE); + + silc_buffer_free(idp); +} + +/* Send New Channel Payload to notify about newly created channel in the + SILC network. Normal server nevers sends this packet. Router uses this + to notify other routers in the network about new channel. This packet + is broadcasted. */ + +void silc_server_send_new_channel(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + char *channel_name, + void *channel_id, + uint32 channel_id_len, + uint32 mode) +{ + SilcBuffer packet; + unsigned char *cid; + uint32 name_len = strlen(channel_name); + + SILC_LOG_DEBUG(("Start")); + + cid = silc_id_id2str(channel_id, SILC_ID_CHANNEL); + if (!cid) + return; + + /* Encode the channel payload */ + packet = silc_channel_payload_encode(channel_name, name_len, + cid, channel_id_len, mode); + + silc_server_packet_send(server, sock, SILC_PACKET_NEW_CHANNEL, + broadcast ? SILC_PACKET_FLAG_BROADCAST : 0, + packet->data, packet->len, FALSE); + + /* Send to backup routers if this is being broadcasted to primary + router. */ + if (server->router && server->router->connection && + sock == server->router->connection && broadcast) + silc_server_backup_send(server, NULL, SILC_PACKET_NEW_CHANNEL, 0, + packet->data, packet->len, FALSE, TRUE); + + silc_free(cid); + silc_buffer_free(packet); +} + +/* Send Channel Key payload to distribute the new channel key. Normal server + sends this to router when new client joins to existing channel. Router + sends this to the local server who sent the join command in case where + the channel did not exist yet. Both normal and router servers uses this + also to send this to locally connected clients on the channel. This + must not be broadcasted packet. Routers do not send this to each other. + If `sender is provided then the packet is not sent to that connection since + it originally came from it. */ + +void silc_server_send_channel_key(SilcServer server, + SilcSocketConnection sender, + SilcChannelEntry channel, + unsigned char route) +{ + SilcBuffer packet; + unsigned char *chid; + uint32 tmp_len; + + SILC_LOG_DEBUG(("Start")); + + chid = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + if (!chid) + return; + + /* Encode channel key packet */ + tmp_len = strlen(channel->channel_key->cipher->name); + packet = silc_channel_key_payload_encode(silc_id_get_len(channel->id, + SILC_ID_CHANNEL), + chid, tmp_len, + channel->channel_key->cipher->name, + channel->key_len / 8, channel->key); + silc_server_packet_send_to_channel(server, sender, channel, + SILC_PACKET_CHANNEL_KEY, + route, packet->data, packet->len, FALSE); + silc_buffer_free(packet); + silc_free(chid); +} + +/* Generic function to send any command. The arguments must be sent already + encoded into correct form in correct order. */ + +void silc_server_send_command(SilcServer server, + SilcSocketConnection sock, + SilcCommand command, + uint16 ident, + uint32 argc, ...) +{ + SilcBuffer packet; + va_list ap; + + va_start(ap, argc); + + packet = silc_command_payload_encode_vap(command, ident, argc, ap); + silc_server_packet_send(server, sock, SILC_PACKET_COMMAND, 0, + packet->data, packet->len, TRUE); + silc_buffer_free(packet); + va_end(ap); +} + +/* Send the heartbeat packet. */ + +void silc_server_send_heartbeat(SilcServer server, + SilcSocketConnection sock) +{ + silc_server_packet_send(server, sock, SILC_PACKET_HEARTBEAT, 0, + NULL, 0, FALSE); +} + +/* Generic function to relay packet we've received. This is used to relay + packets to a client but generally can be used to other purposes as well. */ + +void silc_server_relay_packet(SilcServer server, + SilcSocketConnection dst_sock, + SilcCipher cipher, + SilcHmac hmac, + uint32 sequence, + SilcPacketContext *packet, + bool force_send) +{ + silc_buffer_push(packet->buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len + + packet->dst_id_len + packet->padlen); + + silc_packet_send_prepare(dst_sock, 0, 0, packet->buffer->len); + silc_buffer_put(dst_sock->outbuf, packet->buffer->data, packet->buffer->len); + + /* Re-encrypt packet */ + silc_packet_encrypt(cipher, hmac, sequence, dst_sock->outbuf, + packet->buffer->len); + + /* Send the packet */ + silc_server_packet_send_real(server, dst_sock, force_send); + + silc_buffer_pull(packet->buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len + + packet->dst_id_len + packet->padlen); +} + +/* Routine used to send the connection authentication packet. */ + +void silc_server_send_connection_auth_request(SilcServer server, + SilcSocketConnection sock, + uint16 conn_type, + SilcAuthMethod auth_meth) +{ + SilcBuffer packet; + + packet = silc_buffer_alloc(4); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + silc_buffer_format(packet, + SILC_STR_UI_SHORT(conn_type), + SILC_STR_UI_SHORT(auth_meth), + SILC_STR_END); + + silc_server_packet_send(server, sock, SILC_PACKET_CONNECTION_AUTH_REQUEST, + 0, packet->data, packet->len, FALSE); + silc_buffer_free(packet); +} + +/* Purge the outgoing packet queue to the network if there is data. This + function can be used to empty the packet queue. It is guaranteed that + after this function returns the outgoing data queue is empty. */ + +void silc_server_packet_queue_purge(SilcServer server, + SilcSocketConnection sock) +{ + if (sock && SILC_IS_OUTBUF_PENDING(sock) && + (SILC_IS_DISCONNECTED(sock) == FALSE)) { + server->stat.packets_sent++; + + if (sock->outbuf->data - sock->outbuf->head) + silc_buffer_push(sock->outbuf, sock->outbuf->data - sock->outbuf->head); + + silc_packet_send(sock, TRUE); + + SILC_SET_CONNECTION_FOR_INPUT(server->schedule, sock->sock); + SILC_UNSET_OUTBUF_PENDING(sock); + silc_buffer_clear(sock->outbuf); + } +} diff --git a/apps/silcd/packet_send.h b/apps/silcd/packet_send.h new file mode 100644 index 00000000..48e12977 --- /dev/null +++ b/apps/silcd/packet_send.h @@ -0,0 +1,244 @@ +/* + + packet_send.h + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2001 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. + +*/ + +#ifndef PACKET_SEND_H +#define PACKET_SEND_H + +/* Prototypes */ + +int silc_server_packet_send_real(SilcServer server, + SilcSocketConnection sock, + bool force_send); +void silc_server_packet_send(SilcServer server, + SilcSocketConnection sock, + SilcPacketType type, + SilcPacketFlags flags, + unsigned char *data, + uint32 data_len, + bool force_send); +void silc_server_packet_send_dest(SilcServer server, + SilcSocketConnection sock, + SilcPacketType type, + SilcPacketFlags flags, + void *dst_id, + SilcIdType dst_id_type, + unsigned char *data, + uint32 data_len, + bool force_send); +void silc_server_packet_send_srcdest(SilcServer server, + SilcSocketConnection sock, + SilcPacketType type, + SilcPacketFlags flags, + void *src_id, + SilcIdType src_id_type, + void *dst_id, + SilcIdType dst_id_type, + unsigned char *data, + uint32 data_len, + bool force_send); +void silc_server_packet_broadcast(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_packet_route(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_packet_send_clients(SilcServer server, + SilcClientEntry *clients, + uint32 clients_count, + SilcPacketType type, + SilcPacketFlags flags, + bool route, + unsigned char *data, + uint32 data_len, + bool force_send); +void silc_server_packet_send_to_channel(SilcServer server, + SilcSocketConnection sender, + SilcChannelEntry channel, + SilcPacketType type, + bool route, + unsigned char *data, + uint32 data_len, + bool force_send); +void silc_server_packet_relay_to_channel(SilcServer server, + SilcSocketConnection sender_sock, + SilcChannelEntry channel, + void *sender, + SilcIdType sender_type, + void *sender_entry, + unsigned char *data, + uint32 data_len, + bool force_send); +void silc_server_packet_send_local_channel(SilcServer server, + SilcChannelEntry channel, + SilcPacketType type, + SilcPacketFlags flags, + unsigned char *data, + uint32 data_len, + bool force_send); +void silc_server_send_private_message(SilcServer server, + SilcSocketConnection dst_sock, + SilcCipher cipher, + SilcHmac hmac, + uint32 sequence, + SilcPacketContext *packet); +void silc_server_send_motd(SilcServer server, + SilcSocketConnection sock); +void silc_server_send_error(SilcServer server, + SilcSocketConnection sock, + const char *fmt, ...); +void silc_server_send_notify(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcNotifyType type, + uint32 argc, ...); +void silc_server_send_notify_args(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcNotifyType type, + uint32 argc, + SilcBuffer args); +void silc_server_send_notify_channel_change(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelID *old_id, + SilcChannelID *new_id); +void silc_server_send_notify_nick_change(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *old_id, + SilcClientID *new_id); +void silc_server_send_notify_join(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id); +void silc_server_send_notify_leave(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id); +void silc_server_send_notify_cmode(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + uint32 mode_mask, + void *id, SilcIdType id_type, + char *cipher, char *hmac); +void silc_server_send_notify_cumode(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + uint32 mode_mask, + void *id, SilcIdType id_type, + SilcClientID *target); +void silc_server_send_notify_signoff(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *client_id, + char *message); +void silc_server_send_notify_topic_set(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id, + char *topic); +void silc_server_send_notify_kicked(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id, + char *comment); +void silc_server_send_notify_killed(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *client_id, + char *comment); +void silc_server_send_notify_umode(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcClientID *client_id, + uint32 mode_mask); +void silc_server_send_notify_ban(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + char *add, char *del); +void silc_server_send_notify_invite(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + SilcChannelEntry channel, + SilcClientID *client_id, + char *add, char *del); +void silc_server_send_notify_dest(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + void *dest_id, + SilcIdType dest_id_type, + SilcNotifyType type, + uint32 argc, ...); +void silc_server_send_notify_to_channel(SilcServer server, + SilcSocketConnection sender, + SilcChannelEntry channel, + unsigned char route_notify, + SilcNotifyType type, + uint32 argc, ...); +void silc_server_send_notify_on_channels(SilcServer server, + SilcClientEntry sender, + SilcClientEntry client, + SilcNotifyType type, + uint32 argc, ...); +void silc_server_send_new_id(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + void *id, SilcIdType id_type, + uint32 id_len); +void silc_server_send_new_channel(SilcServer server, + SilcSocketConnection sock, + bool broadcast, + char *channel_name, + void *channel_id, + uint32 channel_id_len, + uint32 mode); +void silc_server_send_channel_key(SilcServer server, + SilcSocketConnection sender, + SilcChannelEntry channel, + unsigned char route); +void silc_server_send_command(SilcServer server, + SilcSocketConnection sock, + SilcCommand command, + uint16 ident, + uint32 argc, ...); +void silc_server_send_heartbeat(SilcServer server, + SilcSocketConnection sock); +void silc_server_relay_packet(SilcServer server, + SilcSocketConnection dst_sock, + SilcCipher cipher, + SilcHmac hmac, + uint32 sequence, + SilcPacketContext *packet, + bool force_send); +void silc_server_send_connection_auth_request(SilcServer server, + SilcSocketConnection sock, + uint16 conn_type, + SilcAuthMethod auth_meth); +void silc_server_packet_queue_purge(SilcServer server, + SilcSocketConnection sock); + +#endif diff --git a/apps/silcd/protocol.c b/apps/silcd/protocol.c index 60d49170..c78ff8e2 100644 --- a/apps/silcd/protocol.c +++ b/apps/silcd/protocol.c @@ -2,9 +2,9 @@ protocol.c - Author: Pekka Riikonen + Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -20,39 +20,165 @@ /* * Server side of the protocols. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "serverincludes.h" #include "server_internal.h" SILC_TASK_CALLBACK(silc_server_protocol_connection_auth); -SILC_TASK_CALLBACK(silc_server_protocol_channel_auth); SILC_TASK_CALLBACK(silc_server_protocol_key_exchange); +SILC_TASK_CALLBACK(silc_server_protocol_rekey); -/* SILC client protocol list */ -const SilcProtocolObject silc_protocol_list[] = -{ - { SILC_PROTOCOL_SERVER_CONNECTION_AUTH, - silc_server_protocol_connection_auth }, - { SILC_PROTOCOL_SERVER_CHANNEL_AUTH, - silc_server_protocol_channel_auth }, - { SILC_PROTOCOL_SERVER_KEY_EXCHANGE, - silc_server_protocol_key_exchange }, - - { SILC_PROTOCOL_SERVER_NONE, NULL }, -}; +extern char *silc_version_string; /* * Key Exhange protocol functions */ +static bool +silc_verify_public_key_internal(SilcServer server, SilcSocketConnection sock, + SilcSocketType conn_type, + unsigned char *pk, uint32 pk_len, + SilcSKEPKType pk_type) +{ + char file[256], filename[256], *fingerprint; + struct stat st; + + if (pk_type != SILC_SKE_PK_TYPE_SILC) { + SILC_LOG_WARNING(("We don't support %s (%s) port %d public key type %d", + sock->hostname, sock->ip, sock->port, pk_type)); + return FALSE; + } + + /* Accept client keys without verification */ + if (conn_type == SILC_SOCKET_TYPE_CLIENT) { + SILC_LOG_DEBUG(("Accepting client public key without verification")); + return TRUE; + } + + memset(filename, 0, sizeof(filename)); + memset(file, 0, sizeof(file)); + snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", sock->hostname, + sock->port); + snprintf(filename, sizeof(filename) - 1, SILC_ETCDIR "/serverkeys/%s", + file); + + /* Create serverkeys directory if it doesn't exist. */ + if (stat(SILC_ETCDIR "/serverkeys", &st) < 0) { + /* If dir doesn't exist */ + if (errno == ENOENT) { + if (mkdir(SILC_ETCDIR "/serverkeys", 0755) < 0) { + SILC_LOG_ERROR(("Couldn't create `%s' directory\n", + SILC_ETCDIR "/serverkeys")); + return TRUE; + } + } else { + SILC_LOG_ERROR(("%s\n", strerror(errno))); + return TRUE; + } + } + + /* Take fingerprint of the public key */ + fingerprint = silc_hash_fingerprint(NULL, pk, pk_len); + SILC_LOG_DEBUG(("Received server %s (%s) port %d public key (%s)", + sock->hostname, sock->ip, sock->port, fingerprint)); + silc_free(fingerprint); + + /* Check whether this key already exists */ + if (stat(filename, &st) < 0) { + /* We don't have it, then cache it. */ + SILC_LOG_DEBUG(("New public key from server")); + + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + return TRUE; + } else { + /* The key already exists, verify it. */ + SilcPublicKey public_key; + unsigned char *encpk; + uint32 encpk_len; + + SILC_LOG_DEBUG(("We have the public key saved locally")); + + /* Load the key file */ + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_PEM)) + if (!silc_pkcs_load_public_key(filename, &public_key, + SILC_PKCS_FILE_BIN)) { + SILC_LOG_WARNING(("Could not load local copy of the %s (%s) port %d " + "server public key", sock->hostname, sock->ip, + sock->port)); + + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + return TRUE; + } + + /* Encode the key data */ + encpk = silc_pkcs_public_key_encode(public_key, &encpk_len); + if (!encpk) { + SILC_LOG_WARNING(("Local copy of the server %s (%s) port %d public key " + "is malformed", sock->hostname, sock->ip, sock->port)); + + /* Save the key for future checking */ + unlink(filename); + silc_pkcs_save_public_key_data(filename, pk, pk_len, + SILC_PKCS_FILE_PEM); + return TRUE; + } + + if (memcmp(encpk, pk, encpk_len)) { + SILC_LOG_WARNING(("%s (%s) port %d server public key does not match " + "with local copy", sock->hostname, sock->ip, + sock->port)); + SILC_LOG_WARNING(("It is possible that the key has expired or changed")); + SILC_LOG_WARNING(("It is also possible that some one is performing " + "man-in-the-middle attack")); + SILC_LOG_WARNING(("Will not accept the server %s (%s) port %d public " + "key", + sock->hostname, sock->ip, sock->port)); + return FALSE; + } + + /* Local copy matched */ + return TRUE; + } +} + +/* Callback that is called when we have received KE2 payload from + responder. We try to verify the public key now. */ + +static void +silc_server_protocol_ke_verify_key(SilcSKE ske, + unsigned char *pk_data, + uint32 pk_len, + SilcSKEPKType pk_type, + void *context, + SilcSKEVerifyCbCompletion completion, + void *completion_context) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerKEInternalContext *ctx = + (SilcServerKEInternalContext *)protocol->context; + SilcServer server = (SilcServer)ctx->server; + + SILC_LOG_DEBUG(("Start")); + + if (silc_verify_public_key_internal(server, ctx->sock, + (ctx->responder == FALSE ? + SILC_SOCKET_TYPE_ROUTER: + ctx->sconfig ? SILC_SOCKET_TYPE_SERVER : + ctx->rconfig ? SILC_SOCKET_TYPE_ROUTER : + SILC_SOCKET_TYPE_CLIENT), + pk_data, pk_len, pk_type)) + completion(ske, SILC_SKE_STATUS_OK, completion_context); + else + completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY, + completion_context); +} + /* Packet sending callback. This function is provided as packet sending routine to the Key Exchange functions. */ @@ -73,65 +199,218 @@ static void silc_server_protocol_ke_send_packet(SilcSKE ske, /* Sets the negotiated key material into use for particular connection. */ -static void silc_server_protocol_ke_set_keys(SilcSKE ske, - SilcSocketConnection sock, - SilcSKEKeyMaterial *keymat, - SilcCipher cipher, - SilcPKCS pkcs, - SilcHash hash, - int is_responder) +int silc_server_protocol_ke_set_keys(SilcSKE ske, + SilcSocketConnection sock, + SilcSKEKeyMaterial *keymat, + SilcCipher cipher, + SilcPKCS pkcs, + SilcHash hash, + SilcHmac hmac, + SilcSKEDiffieHellmanGroup group, + bool is_responder) { - SilcIDListUnknown *conn_data; - SilcHash nhash; + SilcUnknownEntry conn_data; + SilcIDListData idata; SILC_LOG_DEBUG(("Setting new key into use")); conn_data = silc_calloc(1, sizeof(*conn_data)); + idata = (SilcIDListData)conn_data; /* Allocate cipher to be used in the communication */ - silc_cipher_alloc(cipher->cipher->name, &conn_data->send_key); - silc_cipher_alloc(cipher->cipher->name, &conn_data->receive_key); + if (!silc_cipher_alloc(cipher->cipher->name, &idata->send_key)) { + silc_free(conn_data); + return FALSE; + } + if (!silc_cipher_alloc(cipher->cipher->name, &idata->receive_key)) { + silc_free(conn_data); + return FALSE; + } + if (!silc_hmac_alloc((char *)silc_hmac_get_name(hmac), NULL, + &idata->hmac_send)) { + silc_cipher_free(idata->send_key); + silc_cipher_free(idata->receive_key); + silc_free(conn_data); + return FALSE; + } + + if (!silc_hmac_alloc((char *)silc_hmac_get_name(hmac), NULL, + &idata->hmac_receive)) { + silc_cipher_free(idata->send_key); + silc_cipher_free(idata->receive_key); + silc_hmac_free(idata->hmac_send); + silc_free(conn_data); + return FALSE; + } + if (is_responder == TRUE) { - conn_data->send_key->cipher->set_key(conn_data->send_key->context, - keymat->receive_enc_key, - keymat->enc_key_len); - conn_data->send_key->set_iv(conn_data->send_key, keymat->receive_iv); - conn_data->receive_key->cipher->set_key(conn_data->receive_key->context, - keymat->send_enc_key, - keymat->enc_key_len); - conn_data->receive_key->set_iv(conn_data->receive_key, keymat->send_iv); - + silc_cipher_set_key(idata->send_key, keymat->receive_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->send_key, keymat->receive_iv); + silc_cipher_set_key(idata->receive_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->receive_key, keymat->send_iv); + silc_hmac_set_key(idata->hmac_send, keymat->receive_hmac_key, + keymat->hmac_key_len); + silc_hmac_set_key(idata->hmac_receive, keymat->send_hmac_key, + keymat->hmac_key_len); } else { - conn_data->send_key->cipher->set_key(conn_data->send_key->context, - keymat->send_enc_key, - keymat->enc_key_len); - conn_data->send_key->set_iv(conn_data->send_key, keymat->send_iv); - conn_data->receive_key->cipher->set_key(conn_data->receive_key->context, - keymat->receive_enc_key, - keymat->enc_key_len); - conn_data->receive_key->set_iv(conn_data->receive_key, keymat->receive_iv); + silc_cipher_set_key(idata->send_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->send_key, keymat->send_iv); + silc_cipher_set_key(idata->receive_key, keymat->receive_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->receive_key, keymat->receive_iv); + silc_hmac_set_key(idata->hmac_send, keymat->send_hmac_key, + keymat->hmac_key_len); + silc_hmac_set_key(idata->hmac_receive, keymat->receive_hmac_key, + keymat->hmac_key_len); + } + + idata->rekey = silc_calloc(1, sizeof(*idata->rekey)); + idata->rekey->send_enc_key = + silc_calloc(keymat->enc_key_len / 8, + sizeof(*idata->rekey->send_enc_key)); + memcpy(idata->rekey->send_enc_key, + keymat->send_enc_key, keymat->enc_key_len / 8); + idata->rekey->enc_key_len = keymat->enc_key_len / 8; + + if (ske->start_payload->flags & SILC_SKE_SP_FLAG_PFS) + idata->rekey->pfs = TRUE; + idata->rekey->ske_group = silc_ske_group_get_number(group); + + /* Save the hash */ + if (!silc_hash_alloc(hash->hash->name, &idata->hash)) { + silc_cipher_free(idata->send_key); + silc_cipher_free(idata->receive_key); + silc_hmac_free(idata->hmac_send); + silc_hmac_free(idata->hmac_receive); + silc_free(conn_data); + return FALSE; } - /* Allocate PKCS to be used */ -#if 0 - /* XXX Do we ever need to allocate PKCS for the connection?? - If yes, we need to change KE protocol to get the initiators - public key. */ - silc_pkcs_alloc(pkcs->pkcs->name, &conn_data->pkcs); - silc_pkcs_set_public_key(conn_data->pkcs, ske->ke2_payload->pk_data, - ske->ke2_payload->pk_len); -#endif - - /* Save HMAC key to be used in the communication. */ - silc_hash_alloc(hash->hash->name, &nhash); - silc_hmac_alloc(nhash, &conn_data->hmac); - conn_data->hmac_key_len = keymat->hmac_key_len; - conn_data->hmac_key = silc_calloc(conn_data->hmac_key_len, - sizeof(unsigned char)); - memcpy(conn_data->hmac_key, keymat->hmac_key, keymat->hmac_key_len); + /* Save the remote host's public key */ + silc_pkcs_public_key_decode(ske->ke1_payload->pk_data, + ske->ke1_payload->pk_len, &idata->public_key); sock->user_data = (void *)conn_data; + + SILC_LOG_INFO(("%s (%s) security properties: %s %s %s", + sock->hostname, sock->ip, + idata->send_key->cipher->name, + (char *)silc_hmac_get_name(idata->hmac_send), + idata->hash->hash->name)); + + return TRUE; +} + +/* Check remote host version string */ + +SilcSKEStatus silc_ske_check_version(SilcSKE ske, unsigned char *version, + uint32 len, void *context) +{ + SilcSKEStatus status = SILC_SKE_STATUS_OK; + char *cp; + int maj = 0, min = 0, build = 0, maj2 = 0, min2 = 0, build2 = 0; + + SILC_LOG_INFO(("%s (%s) is version %s", ske->sock->hostname, + ske->sock->ip, version)); + + /* Check for initial version string */ + if (!strstr(version, "SILC-1.0-")) + status = SILC_SKE_STATUS_BAD_VERSION; + + /* Check software version */ + + cp = version + 9; + if (!cp) + status = SILC_SKE_STATUS_BAD_VERSION; + + maj = atoi(cp); + cp = strchr(cp, '.'); + if (cp) { + min = atoi(cp + 1); + cp++; + } + if (cp) { + cp = strchr(cp, '.'); + if (cp) + build = atoi(cp + 1); + } + + cp = silc_version_string + 9; + if (!cp) + status = SILC_SKE_STATUS_BAD_VERSION; + + maj2 = atoi(cp); + cp = strchr(cp, '.'); + if (cp) { + min2 = atoi(cp + 1); + cp++; + } + if (cp) { + cp = strchr(cp, '.'); + if (cp) + build2 = atoi(cp + 1); + } + + if (maj != maj2) + status = SILC_SKE_STATUS_BAD_VERSION; + if (min > min2) + status = SILC_SKE_STATUS_BAD_VERSION; + + /* XXX < 0.6 is not allowed */ + if (maj == 0 && min < 5) + status = SILC_SKE_STATUS_BAD_VERSION; + + return status; +} + +/* Callback that is called by the SKE to indicate that it is safe to + continue the execution of the protocol. This is used only if we are + initiator. Is given as argument to the silc_ske_initiator_finish or + silc_ske_responder_phase_2 functions. This is called due to the fact + that the public key verification process is asynchronous and we must + not continue the protocl until the public key has been verified and + this callback is called. */ + +static void silc_server_protocol_ke_continue(SilcSKE ske, void *context) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerKEInternalContext *ctx = + (SilcServerKEInternalContext *)protocol->context; + SilcServer server = (SilcServer)ctx->server; + + SILC_LOG_DEBUG(("Start")); + + if (ske->status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(ske->status))); + SILC_LOG_DEBUG(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(ske->status))); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + + /* Send Ok to the other end. We will end the protocol as responder + sends Ok to us when we will take the new keys into use. */ + if (ctx->responder == FALSE) { + silc_ske_end(ctx->ske); + + /* End the protocol on the next round */ + protocol->state = SILC_PROTOCOL_STATE_END; + } + + /* Advance protocol state and call the next state if we are responder. + This happens when this callback was sent to silc_ske_responder_phase_2 + function. */ + if (ctx->responder == TRUE) { + protocol->state++; + silc_protocol_execute(protocol, server->schedule, 0, 100000); + } } /* Performs key exchange protocol. This is used for both initiator @@ -144,7 +423,7 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) SilcServerKEInternalContext *ctx = (SilcServerKEInternalContext *)protocol->context; SilcServer server = (SilcServer)ctx->server; - SilcSKEStatus status = 0; + SilcSKEStatus status = SILC_SKE_STATUS_OK; SILC_LOG_DEBUG(("Start")); @@ -164,41 +443,52 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) /* Allocate Key Exchange object */ ske = silc_ske_alloc(); ctx->ske = ske; + ske->rng = server->rng; + + silc_ske_set_callbacks(ske, silc_server_protocol_ke_send_packet, NULL, + silc_server_protocol_ke_verify_key, + silc_server_protocol_ke_continue, + silc_ske_check_version, context); if (ctx->responder == TRUE) { /* Start the key exchange by processing the received security properties packet from initiator. */ status = silc_ske_responder_start(ske, ctx->rng, ctx->sock, - ctx->packet, NULL, NULL); + silc_version_string, + ctx->packet->buffer, FALSE); } else { SilcSKEStartPayload *start_payload; /* Assemble security properties. */ - silc_ske_assemble_security_properties(ske, &start_payload); + silc_ske_assemble_security_properties(ske, SILC_SKE_SP_FLAG_NONE, + silc_version_string, + &start_payload); /* Start the key exchange by sending our security properties to the remote end. */ status = silc_ske_initiator_start(ske, ctx->rng, ctx->sock, - start_payload, - silc_server_protocol_ke_send_packet, - context); + start_payload); } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { - SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", - status)); - SILC_LOG_DEBUG(("Error (type %d) during Key Exchange protocol", - status)); + SILC_LOG_WARNING(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); + SILC_LOG_DEBUG(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, 0, 300000); return; } /* Advance protocol state and call the next state if we are responder */ protocol->state++; if (ctx->responder == TRUE) - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 100000); + silc_protocol_execute(protocol, server->schedule, 0, 100000); } break; case 2: @@ -208,37 +498,35 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) */ if (ctx->responder == TRUE) { /* Sends the selected security properties to the initiator. */ - status = - silc_ske_responder_phase_1(ctx->ske, - ctx->ske->start_payload, - silc_server_protocol_ke_send_packet, - context); + status = silc_ske_responder_phase_1(ctx->ske, + ctx->ske->start_payload); } else { /* Call Phase-1 function. This processes the Key Exchange Start paylaod reply we just got from the responder. The callback function will receive the processed payload where we will save it. */ - status = - silc_ske_initiator_phase_1(ctx->ske, - ctx->packet, - NULL, NULL); + status = silc_ske_initiator_phase_1(ctx->ske, ctx->packet->buffer); } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { - SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", - status)); - SILC_LOG_DEBUG(("Error (type %d) during Key Exchange protocol", - status)); + SILC_LOG_WARNING(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); + SILC_LOG_DEBUG(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, 0, 300000); return; } /* Advance protocol state and call next state if we are initiator */ protocol->state++; if (ctx->responder == FALSE) - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 100000); + silc_protocol_execute(protocol, server->schedule, 0, 100000); } break; case 3: @@ -249,34 +537,33 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) if (ctx->responder == TRUE) { /* Process the received Key Exchange 1 Payload packet from the initiator. This also creates our parts of the Diffie - Hellman algorithm. */ - status = - silc_ske_responder_phase_2(ctx->ske, ctx->packet, NULL, NULL); + Hellman algorithm. The silc_server_protocol_ke_continue + will be called after the public key has been verified. */ + status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer); } else { /* Call the Phase-2 function. This creates Diffie Hellman key exchange parameters and sends our public part inside Key Exhange 1 Payload to the responder. */ - status = - silc_ske_initiator_phase_2(ctx->ske, - silc_server_protocol_ke_send_packet, - context); + status = silc_ske_initiator_phase_2(ctx->ske, + server->public_key, + server->private_key); + protocol->state++; } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { - SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", - status)); - SILC_LOG_DEBUG(("Error (type %d) during Key Exchange protocol", - status)); + SILC_LOG_WARNING(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); + SILC_LOG_DEBUG(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, 0, 300000); return; } - - /* Advance protocol state and call the next state if we are responder */ - protocol->state++; - if (ctx->responder == TRUE) - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 100000); } break; case 4: @@ -285,109 +572,118 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) * Finish protocol */ if (ctx->responder == TRUE) { - unsigned char *pk, *prv; - unsigned int pk_len, prv_len; - - /* Get our public key to be sent to the initiator */ - pk = silc_pkcs_get_public_key(server->public_key, &pk_len); - - /* Get out private key to sign some data. */ - prv = silc_pkcs_get_private_key(server->public_key, &prv_len); - /* This creates the key exchange material and sends our public parts to the initiator inside Key Exchange 2 Payload. */ - status = - silc_ske_responder_finish(ctx->ske, - pk, pk_len, prv, prv_len, - SILC_SKE_PK_TYPE_SILC, - silc_server_protocol_ke_send_packet, - context); - - memset(pk, 0, pk_len); - memset(prv, 0, prv_len); - silc_free(pk); - silc_free(prv); + status = silc_ske_responder_finish(ctx->ske, + server->public_key, + server->private_key, + SILC_SKE_PK_TYPE_SILC); + + /* End the protocol on the next round */ + protocol->state = SILC_PROTOCOL_STATE_END; } else { /* Finish the protocol. This verifies the Key Exchange 2 payload - sent by responder. */ - status = - silc_ske_initiator_finish(ctx->ske, - ctx->packet, NULL, NULL); + sent by responder. The silc_server_protocol_ke_continue will + be called after the public key has been verified. */ + status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer); } + /* Return now if the procedure is pending. */ + if (status == SILC_SKE_STATUS_PENDING) + return; + if (status != SILC_SKE_STATUS_OK) { - SILC_LOG_WARNING(("Error (type %d) during Key Exchange protocol", - status)); - SILC_LOG_DEBUG(("Error (type %d) during Key Exchange protocol", - status)); + SILC_LOG_WARNING(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); + SILC_LOG_DEBUG(("Error (%s) during Key Exchange protocol", + silc_ske_map_status(status))); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, 0, 300000); return; } - - /* Send Ok to the other end. We will end the protocol as responder - sends Ok to us when we will take the new keys into use. */ - if (ctx->responder == FALSE) - silc_ske_end(ctx->ske, silc_server_protocol_ke_send_packet, context); - - /* End the protocol on the next round */ - protocol->state = SILC_PROTOCOL_STATE_END; } break; + case SILC_PROTOCOL_STATE_END: { /* * End protocol */ SilcSKEKeyMaterial *keymat; - - /* Send Ok to the other end if we are responder. If we are - initiator we have sent this already. */ - if (ctx->responder == TRUE) - silc_ske_end(ctx->ske, silc_server_protocol_ke_send_packet, context); + int key_len = silc_cipher_get_key_len(ctx->ske->prop->cipher); + int hash_len = ctx->ske->prop->hash->hash->hash_len; /* Process the key material */ keymat = silc_calloc(1, sizeof(*keymat)); - silc_ske_process_key_material(ctx->ske, 16, (16 * 8), 16, keymat); + status = silc_ske_process_key_material(ctx->ske, 16, key_len, hash_len, + keymat); + if (status != SILC_SKE_STATUS_OK) { + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + silc_ske_free_key_material(keymat); + return; + } + ctx->keymat = keymat; - /* Take the new keys into use. */ - silc_server_protocol_ke_set_keys(ctx->ske, ctx->sock, keymat, - ctx->ske->prop->cipher, - ctx->ske->prop->pkcs, - ctx->ske->prop->hash, - ctx->responder); + /* Send Ok to the other end if we are responder. If we are initiator + we have sent this already. */ + if (ctx->responder == TRUE) + silc_ske_end(ctx->ske); /* Unregister the timeout task since the protocol has ended. This was the timeout task to be executed if the protocol is not completed fast enough. */ if (ctx->timeout_task) - silc_task_unregister(server->timeout_queue, ctx->timeout_task); + silc_schedule_task_del(server->schedule, ctx->timeout_task); /* Call the final callback */ if (protocol->final_callback) - protocol->execute_final(server->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, server->schedule); else silc_protocol_free(protocol); } break; + case SILC_PROTOCOL_STATE_ERROR: /* * Error occured */ + /* Send abort notification */ + silc_ske_abort(ctx->ske, ctx->ske->status); + + /* Unregister the timeout task since the protocol has ended. + This was the timeout task to be executed if the protocol is + not completed fast enough. */ + if (ctx->timeout_task) + silc_schedule_task_del(server->schedule, ctx->timeout_task); + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_FAILURE: + /* + * We have received failure from remote + */ + /* Unregister the timeout task since the protocol has ended. This was the timeout task to be executed if the protocol is not completed fast enough. */ if (ctx->timeout_task) - silc_task_unregister(server->timeout_queue, ctx->timeout_task); + silc_schedule_task_del(server->schedule, ctx->timeout_task); /* On error the final callback is always called. */ if (protocol->final_callback) - protocol->execute_final(server->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, server->schedule); else silc_protocol_free(protocol); break; + case SILC_PROTOCOL_STATE_UNKNOWN: break; } @@ -397,6 +693,96 @@ SILC_TASK_CALLBACK(silc_server_protocol_key_exchange) * Connection Authentication protocol functions */ +static int +silc_server_password_authentication(SilcServer server, char *auth1, + char *auth2) +{ + if (!auth1 || !auth2) + return FALSE; + + if (!memcmp(auth1, auth2, strlen(auth1))) + return TRUE; + + return FALSE; +} + +static int +silc_server_public_key_authentication(SilcServer server, + SilcPublicKey pub_key, + unsigned char *sign, + uint32 sign_len, + SilcSKE ske) +{ + SilcPKCS pkcs; + int len; + SilcBuffer auth; + + if (!pub_key || !sign) + return FALSE; + + silc_pkcs_alloc(pub_key->name, &pkcs); + if (!silc_pkcs_public_key_set(pkcs, pub_key)) { + silc_pkcs_free(pkcs); + return FALSE; + } + + /* Make the authentication data. Protocol says it is HASH plus + KE Start Payload. */ + len = ske->hash_len + ske->start_payload_copy->len; + auth = silc_buffer_alloc(len); + silc_buffer_pull_tail(auth, len); + silc_buffer_format(auth, + SILC_STR_UI_XNSTRING(ske->hash, ske->hash_len), + SILC_STR_UI_XNSTRING(ske->start_payload_copy->data, + ske->start_payload_copy->len), + SILC_STR_END); + + /* Verify signature */ + if (silc_pkcs_verify_with_hash(pkcs, ske->prop->hash, sign, sign_len, + auth->data, auth->len)) { + silc_pkcs_free(pkcs); + silc_buffer_free(auth); + return TRUE; + } + + silc_pkcs_free(pkcs); + silc_buffer_free(auth); + return FALSE; +} + +static int +silc_server_get_public_key_auth(SilcServer server, + unsigned char *auth_data, + uint32 *auth_data_len, + SilcSKE ske) +{ + int len; + SilcPKCS pkcs; + SilcBuffer auth; + + pkcs = server->pkcs; + + /* Make the authentication data. Protocol says it is HASH plus + KE Start Payload. */ + len = ske->hash_len + ske->start_payload_copy->len; + auth = silc_buffer_alloc(len); + silc_buffer_pull_tail(auth, len); + silc_buffer_format(auth, + SILC_STR_UI_XNSTRING(ske->hash, ske->hash_len), + SILC_STR_UI_XNSTRING(ske->start_payload_copy->data, + ske->start_payload_copy->len), + SILC_STR_END); + + if (silc_pkcs_sign_with_hash(pkcs, ske->prop->hash, auth->data, + auth->len, auth_data, auth_data_len)) { + silc_buffer_free(auth); + return TRUE; + } + + silc_buffer_free(auth); + return FALSE; +} + /* Performs connection authentication protocol. If responder, we authenticate the remote data received. If initiator, we will send authentication data to the remote end. */ @@ -426,22 +812,31 @@ SILC_TASK_CALLBACK(silc_server_protocol_connection_auth) /* * We are receiving party */ - unsigned short payload_len; - unsigned short conn_type; - unsigned char *auth_data; + int ret; + uint16 payload_len; + uint16 conn_type; + unsigned char *auth_data = NULL; + + SILC_LOG_INFO(("Performing authentication protocol for %s (%s)", + ctx->sock->hostname, ctx->sock->ip)); /* Parse the received authentication data packet. The received payload is Connection Auth Payload. */ - silc_buffer_unformat(ctx->packet, - SILC_STR_UI_SHORT(&payload_len), - SILC_STR_UI_SHORT(&conn_type), - SILC_STR_END); + ret = silc_buffer_unformat(ctx->packet->buffer, + SILC_STR_UI_SHORT(&payload_len), + SILC_STR_UI_SHORT(&conn_type), + SILC_STR_END); + if (ret == -1) { + SILC_LOG_DEBUG(("Bad payload in authentication packet")); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } - if (payload_len != ctx->packet->len) { - SILC_LOG_ERROR(("Bad payload in authentication packet")); + if (payload_len != ctx->packet->buffer->len) { SILC_LOG_DEBUG(("Bad payload in authentication packet")); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, 0, 300000); return; } @@ -450,21 +845,24 @@ SILC_TASK_CALLBACK(silc_server_protocol_connection_auth) if (conn_type < SILC_SOCKET_TYPE_CLIENT || conn_type > SILC_SOCKET_TYPE_ROUTER) { SILC_LOG_ERROR(("Bad connection type %d", conn_type)); - SILC_LOG_DEBUG(("Bad connection type %d", conn_type)); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, 0, 300000); return; } if (payload_len > 0) { /* Get authentication data */ - silc_buffer_pull(ctx->packet, 4); - silc_buffer_unformat(ctx->packet, - SILC_STR_UI_XNSTRING_ALLOC(&auth_data, - payload_len), - SILC_STR_END); - } else { - auth_data = NULL; + silc_buffer_pull(ctx->packet->buffer, 4); + ret = silc_buffer_unformat(ctx->packet->buffer, + SILC_STR_UI_XNSTRING_ALLOC(&auth_data, + payload_len), + SILC_STR_END); + if (ret == -1) { + SILC_LOG_DEBUG(("Bad payload in authentication packet")); + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } } /* @@ -477,310 +875,199 @@ SILC_TASK_CALLBACK(silc_server_protocol_connection_auth) /* Remote end is client */ if (conn_type == SILC_SOCKET_TYPE_CLIENT) { - SilcConfigServerSectionClientConnection *client = NULL; - client = - silc_config_server_find_client_conn(server->config, - ctx->sock->ip, - ctx->sock->port); - if (!client) - client = - silc_config_server_find_client_conn(server->config, - ctx->sock->hostname, - ctx->sock->port); + SilcServerConfigSectionClientConnection *client = ctx->cconfig; if (client) { switch(client->auth_meth) { - case SILC_PROTOCOL_CONN_AUTH_NONE: + case SILC_AUTH_NONE: /* No authentication required */ SILC_LOG_DEBUG(("No authentication required")); break; - case SILC_PROTOCOL_CONN_AUTH_PASSWORD: + case SILC_AUTH_PASSWORD: /* Password authentication */ SILC_LOG_DEBUG(("Password authentication")); - if (auth_data) { - if (!memcmp(client->auth_data, auth_data, strlen(auth_data))) { - memset(auth_data, 0, payload_len); - silc_free(auth_data); - auth_data = NULL; - break; - } - } + ret = silc_server_password_authentication(server, auth_data, + client->auth_data); + + if (ret) + break; /* Authentication failed */ SILC_LOG_ERROR(("Authentication failed")); SILC_LOG_DEBUG(("Authentication failed")); + silc_free(auth_data); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; break; - case SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY: + case SILC_AUTH_PUBLIC_KEY: /* Public key authentication */ SILC_LOG_DEBUG(("Public key authentication")); - if (auth_data) { - SilcIDListUnknown *conn_data; - SilcPKCS pkcs; - - conn_data = (SilcIDListUnknown *)ctx->sock->user_data; - - /* Load public key from file */ - if (silc_pkcs_load_public_key(client->auth_data, - &pkcs) == FALSE) { - - /* Authentication failed */ - SILC_LOG_ERROR(("Authentication failed " - "- could not read public key file")); - memset(auth_data, 0, payload_len); - silc_free(auth_data); - auth_data = NULL; - protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); - return; - } - - /* Verify hash value HASH from KE protocol */ - if (pkcs->pkcs->verify(pkcs->context, - auth_data, payload_len, - ctx->ske->hash, - ctx->ske->hash_len) - == TRUE) { - silc_pkcs_free(pkcs); - break; - } - } + ret = silc_server_public_key_authentication(server, + client->auth_data, + auth_data, + payload_len, + ctx->ske); + + if (ret) + break; SILC_LOG_ERROR(("Authentication failed")); SILC_LOG_DEBUG(("Authentication failed")); + silc_free(auth_data); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; } } else { SILC_LOG_DEBUG(("No configuration for remote connection")); SILC_LOG_ERROR(("Remote connection not configured")); SILC_LOG_ERROR(("Authentication failed")); - memset(auth_data, 0, payload_len); silc_free(auth_data); - auth_data = NULL; protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; } } /* Remote end is server */ if (conn_type == SILC_SOCKET_TYPE_SERVER) { - SilcConfigServerSectionServerConnection *serv = NULL; - serv = - silc_config_server_find_server_conn(server->config, - ctx->sock->ip, - ctx->sock->port); - if (!serv) - serv = - silc_config_server_find_server_conn(server->config, - ctx->sock->hostname, - ctx->sock->port); + SilcServerConfigSectionServerConnection *serv = ctx->sconfig; if (serv) { switch(serv->auth_meth) { - case SILC_PROTOCOL_CONN_AUTH_NONE: + case SILC_AUTH_NONE: /* No authentication required */ SILC_LOG_DEBUG(("No authentication required")); break; - case SILC_PROTOCOL_CONN_AUTH_PASSWORD: + case SILC_AUTH_PASSWORD: /* Password authentication */ SILC_LOG_DEBUG(("Password authentication")); - if (auth_data) { - if (!memcmp(serv->auth_data, auth_data, strlen(auth_data))) { - memset(auth_data, 0, payload_len); - silc_free(auth_data); - auth_data = NULL; - break; - } - } + ret = silc_server_password_authentication(server, auth_data, + serv->auth_data); + + if (ret) + break; /* Authentication failed */ SILC_LOG_ERROR(("Authentication failed")); SILC_LOG_DEBUG(("Authentication failed")); + silc_free(auth_data); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; break; - - case SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY: + + case SILC_AUTH_PUBLIC_KEY: /* Public key authentication */ SILC_LOG_DEBUG(("Public key authentication")); - if (auth_data) { - SilcIDListUnknown *conn_data; - SilcPKCS pkcs; - - conn_data = (SilcIDListUnknown *)ctx->sock->user_data; - - /* Load public key from file */ - if (silc_pkcs_load_public_key(serv->auth_data, - &pkcs) == FALSE) { - - /* Authentication failed */ - SILC_LOG_ERROR(("Authentication failed " - "- could not read public key file")); - memset(auth_data, 0, payload_len); - silc_free(auth_data); - auth_data = NULL; - protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); - return; - } - - /* Verify hash value HASH from KE protocol */ - if (pkcs->pkcs->verify(pkcs->context, - auth_data, payload_len, - ctx->ske->hash, - ctx->ske->hash_len) - == TRUE) { - silc_pkcs_free(pkcs); - break; - } - } + ret = silc_server_public_key_authentication(server, + serv->auth_data, + auth_data, + payload_len, + ctx->ske); + + if (ret) + break; SILC_LOG_ERROR(("Authentication failed")); SILC_LOG_DEBUG(("Authentication failed")); + silc_free(auth_data); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; } } else { SILC_LOG_DEBUG(("No configuration for remote connection")); SILC_LOG_ERROR(("Remote connection not configured")); SILC_LOG_ERROR(("Authentication failed")); - memset(auth_data, 0, payload_len); - silc_free(auth_data); - auth_data = NULL; protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); + silc_free(auth_data); return; } } /* Remote end is router */ if (conn_type == SILC_SOCKET_TYPE_ROUTER) { - SilcConfigServerSectionServerConnection *serv = NULL; - serv = - silc_config_server_find_router_conn(server->config, - ctx->sock->ip, - ctx->sock->port); - if (!serv) - serv = - silc_config_server_find_router_conn(server->config, - ctx->sock->hostname, - ctx->sock->port); - + SilcServerConfigSectionServerConnection *serv = ctx->rconfig; + if (serv) { switch(serv->auth_meth) { - case SILC_PROTOCOL_CONN_AUTH_NONE: + case SILC_AUTH_NONE: /* No authentication required */ SILC_LOG_DEBUG(("No authentication required")); break; - case SILC_PROTOCOL_CONN_AUTH_PASSWORD: + case SILC_AUTH_PASSWORD: /* Password authentication */ SILC_LOG_DEBUG(("Password authentication")); - if (auth_data) { - if (!memcmp(serv->auth_data, auth_data, strlen(auth_data))) { - memset(auth_data, 0, payload_len); - silc_free(auth_data); - auth_data = NULL; - break; - } - } + ret = silc_server_password_authentication(server, auth_data, + serv->auth_data); + + if (ret) + break; /* Authentication failed */ SILC_LOG_ERROR(("Authentication failed")); SILC_LOG_DEBUG(("Authentication failed")); + silc_free(auth_data); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; break; - case SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY: + case SILC_AUTH_PUBLIC_KEY: /* Public key authentication */ SILC_LOG_DEBUG(("Public key authentication")); - if (auth_data) { - SilcIDListUnknown *conn_data; - SilcPKCS pkcs; - - conn_data = (SilcIDListUnknown *)ctx->sock->user_data; - - /* Load public key from file */ - if (silc_pkcs_load_public_key(serv->auth_data, - &pkcs) == FALSE) { - - /* Authentication failed */ - SILC_LOG_ERROR(("Authentication failed " - "- could not read public key file")); - memset(auth_data, 0, payload_len); - silc_free(auth_data); - auth_data = NULL; - protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); - return; - } - - /* Verify hash value HASH from KE protocol */ - if (pkcs->pkcs->verify(pkcs->context, - auth_data, payload_len, - ctx->ske->hash, - ctx->ske->hash_len) - == TRUE) { - silc_pkcs_free(pkcs); - break; - } - } - + ret = silc_server_public_key_authentication(server, + serv->auth_data, + auth_data, + payload_len, + ctx->ske); + + if (ret) + break; + SILC_LOG_ERROR(("Authentication failed")); SILC_LOG_DEBUG(("Authentication failed")); + silc_free(auth_data); protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; } } else { SILC_LOG_DEBUG(("No configuration for remote connection")); SILC_LOG_ERROR(("Remote connection not configured")); SILC_LOG_ERROR(("Authentication failed")); - memset(auth_data, 0, payload_len); silc_free(auth_data); - auth_data = NULL; protocol->state = SILC_PROTOCOL_STATE_ERROR; - protocol->execute(server->timeout_queue, 0, - protocol, fd, 0, 300000); + silc_protocol_execute(protocol, server->schedule, + 0, 300000); return; } } - if (auth_data) { - memset(auth_data, 0, payload_len); - silc_free(auth_data); - } - + silc_free(auth_data); + /* Save connection type. This is later used to create the ID for the connection. */ ctx->conn_type = conn_type; /* Advance protocol state. */ protocol->state = SILC_PROTOCOL_STATE_END; - protocol->execute(server->timeout_queue, 0, protocol, fd, 0, 0); + silc_protocol_execute(protocol, server->schedule, 0, 0); } else { /* @@ -791,30 +1078,33 @@ SILC_TASK_CALLBACK(silc_server_protocol_connection_auth) SilcBuffer packet; int payload_len = 0; unsigned char *auth_data = NULL; - unsigned int auth_data_len = 0; + uint32 auth_data_len = 0; switch(ctx->auth_meth) { - case SILC_PROTOCOL_CONN_AUTH_NONE: + case SILC_AUTH_NONE: /* No authentication required */ break; - case SILC_PROTOCOL_CONN_AUTH_PASSWORD: + case SILC_AUTH_PASSWORD: /* Password authentication */ if (ctx->auth_data && ctx->auth_data_len) { - auth_data = ctx->auth_data; + auth_data = strdup(ctx->auth_data); auth_data_len = ctx->auth_data_len; break; } - - /* No authentication data exits. Ask interactively from user. */ - /* XXX */ - break; - case SILC_PROTOCOL_CONN_AUTH_PUBLIC_KEY: - /* Public key authentication */ - /* XXX TODO */ - break; + case SILC_AUTH_PUBLIC_KEY: + { + unsigned char sign[1024]; + + /* Public key authentication */ + silc_server_get_public_key_auth(server, sign, &auth_data_len, + ctx->ske); + auth_data = silc_calloc(auth_data_len, sizeof(*auth_data)); + memcpy(auth_data, sign, auth_data_len); + break; + } } payload_len = 4 + auth_data_len; @@ -851,20 +1141,23 @@ SILC_TASK_CALLBACK(silc_server_protocol_connection_auth) /* * End protocol */ + unsigned char ok[4]; - /* Succesfully authenticated */ - silc_server_packet_send(server, ctx->sock, SILC_PACKET_SUCCESS, - 0, NULL, 0, TRUE); + SILC_PUT32_MSB(SILC_AUTH_OK, ok); + + /* Authentication successful */ + silc_server_packet_send(server, ctx->sock, SILC_PACKET_SUCCESS, + 0, ok, 4, TRUE); /* Unregister the timeout task since the protocol has ended. This was the timeout task to be executed if the protocol is not completed fast enough. */ if (ctx->timeout_task) - silc_task_unregister(server->timeout_queue, ctx->timeout_task); + silc_schedule_task_del(server->schedule, ctx->timeout_task); /* Protocol has ended, call the final callback */ if (protocol->final_callback) - protocol->execute_final(server->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, server->schedule); else silc_protocol_free(protocol); } @@ -872,31 +1165,476 @@ SILC_TASK_CALLBACK(silc_server_protocol_connection_auth) case SILC_PROTOCOL_STATE_ERROR: { /* - * Error + * Error. Send notify to remote. */ + unsigned char error[4]; + + SILC_PUT32_MSB(SILC_AUTH_FAILED, error); /* Authentication failed */ silc_server_packet_send(server, ctx->sock, SILC_PACKET_FAILURE, - 0, NULL, 0, TRUE); + 0, error, 4, TRUE); /* Unregister the timeout task since the protocol has ended. This was the timeout task to be executed if the protocol is not completed fast enough. */ if (ctx->timeout_task) - silc_task_unregister(server->timeout_queue, ctx->timeout_task); + silc_schedule_task_del(server->schedule, ctx->timeout_task); /* On error the final callback is always called. */ if (protocol->final_callback) - protocol->execute_final(server->timeout_queue, 0, protocol, fd); + silc_protocol_execute_final(protocol, server->schedule); else silc_protocol_free(protocol); } break; + + case SILC_PROTOCOL_STATE_FAILURE: + /* + * We have received failure from remote + */ + + /* Unregister the timeout task since the protocol has ended. + This was the timeout task to be executed if the protocol is + not completed fast enough. */ + if (ctx->timeout_task) + silc_schedule_task_del(server->schedule, ctx->timeout_task); + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_UNKNOWN: + break; + } +} + +/* + * Re-key protocol routines + */ + +/* Actually takes the new keys into use. */ + +static void +silc_server_protocol_rekey_validate(SilcServer server, + SilcServerRekeyInternalContext *ctx, + SilcIDListData idata, + SilcSKEKeyMaterial *keymat, + bool send) +{ + if (ctx->responder == TRUE) { + if (send) { + silc_cipher_set_key(idata->send_key, keymat->receive_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->send_key, keymat->receive_iv); + silc_hmac_set_key(idata->hmac_send, keymat->receive_hmac_key, + keymat->hmac_key_len); + } else { + silc_cipher_set_key(idata->receive_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->receive_key, keymat->send_iv); + silc_hmac_set_key(idata->hmac_receive, keymat->send_hmac_key, + keymat->hmac_key_len); + } + } else { + if (send) { + silc_cipher_set_key(idata->send_key, keymat->send_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->send_key, keymat->send_iv); + silc_hmac_set_key(idata->hmac_send, keymat->send_hmac_key, + keymat->hmac_key_len); + } else { + silc_cipher_set_key(idata->receive_key, keymat->receive_enc_key, + keymat->enc_key_len); + silc_cipher_set_iv(idata->receive_key, keymat->receive_iv); + silc_hmac_set_key(idata->hmac_receive, keymat->receive_hmac_key, + keymat->hmac_key_len); + } + } + + /* Save the current sending encryption key */ + if (!send) { + memset(idata->rekey->send_enc_key, 0, idata->rekey->enc_key_len); + silc_free(idata->rekey->send_enc_key); + idata->rekey->send_enc_key = + silc_calloc(keymat->enc_key_len / 8, + sizeof(*idata->rekey->send_enc_key)); + memcpy(idata->rekey->send_enc_key, keymat->send_enc_key, + keymat->enc_key_len / 8); + idata->rekey->enc_key_len = keymat->enc_key_len / 8; + } +} + +/* This function actually re-generates (when not using PFS) the keys and + takes them into use. */ + +void silc_server_protocol_rekey_generate(SilcServer server, + SilcServerRekeyInternalContext *ctx, + bool send) +{ + SilcIDListData idata = (SilcIDListData)ctx->sock->user_data; + SilcSKEKeyMaterial *keymat; + uint32 key_len = silc_cipher_get_key_len(idata->send_key); + uint32 hash_len = idata->hash->hash->hash_len; + + SILC_LOG_DEBUG(("Generating new %s session keys (no PFS)", + send ? "sending" : "receiving")); + + /* Generate the new key */ + keymat = silc_calloc(1, sizeof(*keymat)); + silc_ske_process_key_material_data(idata->rekey->send_enc_key, + idata->rekey->enc_key_len, + 16, key_len, hash_len, + idata->hash, keymat); + + /* Set the keys into use */ + silc_server_protocol_rekey_validate(server, ctx, idata, keymat, send); + + silc_ske_free_key_material(keymat); +} + +/* This function actually re-generates (with PFS) the keys and + takes them into use. */ + +void +silc_server_protocol_rekey_generate_pfs(SilcServer server, + SilcServerRekeyInternalContext *ctx, + bool send) +{ + SilcIDListData idata = (SilcIDListData)ctx->sock->user_data; + SilcSKEKeyMaterial *keymat; + uint32 key_len = silc_cipher_get_key_len(idata->send_key); + uint32 hash_len = idata->hash->hash->hash_len; + unsigned char *tmpbuf; + uint32 klen; + + SILC_LOG_DEBUG(("Generating new %s session keys (with PFS)", + send ? "sending" : "receiving")); + + /* Encode KEY to binary data */ + tmpbuf = silc_mp_mp2bin(ctx->ske->KEY, 0, &klen); + + /* Generate the new key */ + keymat = silc_calloc(1, sizeof(*keymat)); + silc_ske_process_key_material_data(tmpbuf, klen, 16, key_len, hash_len, + idata->hash, keymat); + + /* Set the keys into use */ + silc_server_protocol_rekey_validate(server, ctx, idata, keymat, send); + + memset(tmpbuf, 0, klen); + silc_free(tmpbuf); + silc_ske_free_key_material(keymat); +} + +/* Packet sending callback. This function is provided as packet sending + routine to the Key Exchange functions. */ + +static void +silc_server_protocol_rekey_send_packet(SilcSKE ske, + SilcBuffer packet, + SilcPacketType type, + void *context) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerRekeyInternalContext *ctx = + (SilcServerRekeyInternalContext *)protocol->context; + SilcServer server = (SilcServer)ctx->server; + + /* Send the packet immediately */ + silc_server_packet_send(server, ctx->sock, + type, 0, packet->data, packet->len, FALSE); +} + +/* Performs re-key as defined in the SILC protocol specification. */ + +SILC_TASK_CALLBACK(silc_server_protocol_rekey) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerRekeyInternalContext *ctx = + (SilcServerRekeyInternalContext *)protocol->context; + SilcServer server = (SilcServer)ctx->server; + SilcIDListData idata = (SilcIDListData)ctx->sock->user_data; + SilcSKEStatus status; + + SILC_LOG_DEBUG(("Start")); + + if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN) + protocol->state = SILC_PROTOCOL_STATE_START; + + SILC_LOG_DEBUG(("State=%d", protocol->state)); + + switch(protocol->state) { + case SILC_PROTOCOL_STATE_START: + { + /* + * Start protocol. + */ + + if (ctx->responder == TRUE) { + /* + * We are receiving party + */ + + if (ctx->pfs == TRUE) { + /* + * Use Perfect Forward Secrecy, ie. negotiate the key material + * using the SKE protocol. + */ + + if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_1) { + /* Error in protocol */ + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + + ctx->ske = silc_ske_alloc(); + ctx->ske->rng = server->rng; + ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop)); + silc_ske_get_group_by_number(idata->rekey->ske_group, + &ctx->ske->prop->group); + + silc_ske_set_callbacks(ctx->ske, + silc_server_protocol_rekey_send_packet, + NULL, NULL, NULL, silc_ske_check_version, + context); + + status = silc_ske_responder_phase_2(ctx->ske, ctx->packet->buffer); + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (%s) during Re-key (PFS)", + silc_ske_map_status(status))); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + + /* Advance the protocol state */ + protocol->state++; + silc_protocol_execute(protocol, server->schedule, 0, 0); + } else { + /* + * Do normal and simple re-key. + */ + + /* Send the REKEY_DONE to indicate we will take new keys into use */ + silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY_DONE, + 0, NULL, 0, FALSE); + + /* After we send REKEY_DONE we must set the sending encryption + key to the new key since all packets after this packet must + encrypted with the new key. */ + silc_server_protocol_rekey_generate(server, ctx, TRUE); + + /* The protocol ends in next stage. */ + protocol->state = SILC_PROTOCOL_STATE_END; + } + + } else { + /* + * We are the initiator of this protocol + */ + + /* Start the re-key by sending the REKEY packet */ + silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY, + 0, NULL, 0, FALSE); + + if (ctx->pfs == TRUE) { + /* + * Use Perfect Forward Secrecy, ie. negotiate the key material + * using the SKE protocol. + */ + ctx->ske = silc_ske_alloc(); + ctx->ske->rng = server->rng; + ctx->ske->prop = silc_calloc(1, sizeof(*ctx->ske->prop)); + silc_ske_get_group_by_number(idata->rekey->ske_group, + &ctx->ske->prop->group); + + silc_ske_set_callbacks(ctx->ske, + silc_server_protocol_rekey_send_packet, + NULL, NULL, NULL, silc_ske_check_version, + context); + + status = silc_ske_initiator_phase_2(ctx->ske, NULL, NULL); + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (%s) during Re-key (PFS)", + silc_ske_map_status(status))); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + + /* Advance the protocol state */ + protocol->state++; + } else { + /* + * Do normal and simple re-key. + */ + + /* Send the REKEY_DONE to indicate we will take new keys into use + now. */ + silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY_DONE, + 0, NULL, 0, FALSE); + + /* After we send REKEY_DONE we must set the sending encryption + key to the new key since all packets after this packet must + encrypted with the new key. */ + silc_server_protocol_rekey_generate(server, ctx, TRUE); + + /* The protocol ends in next stage. */ + protocol->state = SILC_PROTOCOL_STATE_END; + } + } + } + break; + + case 2: + /* + * Second state, used only when oding re-key with PFS. + */ + if (ctx->responder == TRUE) { + if (ctx->pfs == TRUE) { + /* + * Send our KE packe to the initiator now that we've processed + * the initiator's KE packet. + */ + status = silc_ske_responder_finish(ctx->ske, NULL, NULL, + SILC_SKE_PK_TYPE_SILC); + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (%s) during Re-key (PFS)", + silc_ske_map_status(status))); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + } + + } else { + if (ctx->pfs == TRUE) { + /* + * The packet type must be KE packet + */ + if (ctx->packet->type != SILC_PACKET_KEY_EXCHANGE_2) { + /* Error in protocol */ + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + + status = silc_ske_initiator_finish(ctx->ske, ctx->packet->buffer); + if (status != SILC_SKE_STATUS_OK) { + SILC_LOG_WARNING(("Error (%s) during Re-key (PFS)", + silc_ske_map_status(status))); + + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + } + } + + /* Send the REKEY_DONE to indicate we will take new keys into use + now. */ + silc_server_packet_send(server, ctx->sock, SILC_PACKET_REKEY_DONE, + 0, NULL, 0, FALSE); + + /* After we send REKEY_DONE we must set the sending encryption + key to the new key since all packets after this packet must + encrypted with the new key. */ + silc_server_protocol_rekey_generate_pfs(server, ctx, TRUE); + + /* The protocol ends in next stage. */ + protocol->state = SILC_PROTOCOL_STATE_END; + break; + + case SILC_PROTOCOL_STATE_END: + /* + * End protocol + */ + + if (ctx->packet->type != SILC_PACKET_REKEY_DONE) { + /* Error in protocol */ + protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute(protocol, server->schedule, 0, 300000); + return; + } + + /* We received the REKEY_DONE packet and all packets after this is + encrypted with the new key so set the decryption key to the new key */ + silc_server_protocol_rekey_generate(server, ctx, FALSE); + + /* Protocol has ended, call the final callback */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_ERROR: + /* + * Error occured + */ + + if (ctx->pfs == TRUE) { + /* Send abort notification */ + silc_ske_abort(ctx->ske, ctx->ske->status); + } + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_FAILURE: + /* + * We have received failure from remote + */ + + /* On error the final callback is always called. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + break; + case SILC_PROTOCOL_STATE_UNKNOWN: break; } + } -SILC_TASK_CALLBACK(silc_server_protocol_channel_auth) +/* Registers protocols used in server. */ + +void silc_server_protocols_register(void) +{ + silc_protocol_register(SILC_PROTOCOL_SERVER_CONNECTION_AUTH, + silc_server_protocol_connection_auth); + silc_protocol_register(SILC_PROTOCOL_SERVER_KEY_EXCHANGE, + silc_server_protocol_key_exchange); + silc_protocol_register(SILC_PROTOCOL_SERVER_REKEY, + silc_server_protocol_rekey); + silc_protocol_register(SILC_PROTOCOL_SERVER_BACKUP, + silc_server_protocol_backup); +} + +/* Unregisters protocols */ + +void silc_server_protocols_unregister(void) { + silc_protocol_unregister(SILC_PROTOCOL_SERVER_CONNECTION_AUTH, + silc_server_protocol_connection_auth); + silc_protocol_unregister(SILC_PROTOCOL_SERVER_KEY_EXCHANGE, + silc_server_protocol_key_exchange); + silc_protocol_unregister(SILC_PROTOCOL_SERVER_REKEY, + silc_server_protocol_rekey); + silc_protocol_unregister(SILC_PROTOCOL_SERVER_BACKUP, + silc_server_protocol_backup); } diff --git a/apps/silcd/protocol.h b/apps/silcd/protocol.h index b9505cf4..21c3d193 100644 --- a/apps/silcd/protocol.h +++ b/apps/silcd/protocol.h @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -22,38 +22,47 @@ #define PROTOCOL_H /* SILC client protocol types */ -#define SILC_PROTOCOL_SERVER_NONE 0 -#define SILC_PROTOCOL_SERVER_CONNECTION_AUTH 1 -#define SILC_PROTOCOL_SERVER_CHANNEL_AUTH 2 -#define SILC_PROTOCOL_SERVER_KEY_EXCHANGE 3 -/* #define SILC_PROTOCOL_SERVER_MAX 255 */ +#define SILC_PROTOCOL_SERVER_NONE 0 +#define SILC_PROTOCOL_SERVER_CONNECTION_AUTH 1 +#define SILC_PROTOCOL_SERVER_KEY_EXCHANGE 2 +#define SILC_PROTOCOL_SERVER_REKEY 3 +#define SILC_PROTOCOL_SERVER_BACKUP 4 +/* #define SILC_PROTOCOL_SERVER_MAX 255 */ /* Internal context for Key Exchange protocol. */ typedef struct { void *server; + void *context; SilcSocketConnection sock; SilcRng rng; /* TRUE if we are receiveing part of the protocol */ - int responder; + bool responder; /* Destinations ID taken from authenticataed packet so that we can get the destinations ID. */ void *dest_id; SilcIdType dest_id_type; + /* Pointer to the configurations. */ + void *cconfig; + void *sconfig; + void *rconfig; + SilcTask timeout_task; - SilcBuffer packet; + SilcPacketContext *packet; SilcSKE ske; + SilcSKEKeyMaterial *keymat; } SilcServerKEInternalContext; /* Internal context for connection authentication protocol */ typedef struct { void *server; + void *context; SilcSocketConnection sock; /* TRUE if we are receiving part of the protocol */ - int responder; + bool responder; /* SKE object from Key Exchange protocol. */ SilcSKE ske; @@ -61,23 +70,50 @@ typedef struct { /* Auth method that must be used. This is resolved before this connection authentication protocol is started. Used when we are initiating. */ - unsigned int auth_meth; + uint32 auth_meth; /* Authentication data if we alreay know it. This is filled before starting the protocol if we know the authentication data. Otherwise these are and remain NULL. Used when we are initiating. */ - unsigned char *auth_data; - unsigned int auth_data_len; + void *auth_data; + uint32 auth_data_len; /* Destinations ID from KE protocol context */ void *dest_id; SilcIdType dest_id_type; + /* Pointer to the configurations. */ + void *cconfig; + void *sconfig; + void *rconfig; + SilcTask timeout_task; - SilcBuffer packet; - unsigned short conn_type; + SilcPacketContext *packet; + uint16 conn_type; } SilcServerConnAuthInternalContext; +/* Internal context for the rekey protocol */ +typedef struct { + void *server; + void *context; + SilcSocketConnection sock; + bool responder; /* TRUE if we are receiving party */ + bool pfs; /* TRUE if PFS is to be used */ + SilcSKE ske; /* Defined if PFS is used */ + SilcPacketContext *packet; +} SilcServerRekeyInternalContext; + /* Prototypes */ +void silc_server_protocols_register(void); +void silc_server_protocols_unregister(void); +int silc_server_protocol_ke_set_keys(SilcSKE ske, + SilcSocketConnection sock, + SilcSKEKeyMaterial *keymat, + SilcCipher cipher, + SilcPKCS pkcs, + SilcHash hash, + SilcHmac hmac, + SilcSKEDiffieHellmanGroup group, + bool is_responder); #endif diff --git a/apps/silcd/pubkey.pub b/apps/silcd/pubkey.pub deleted file mode 100644 index cf4ed9bf..00000000 Binary files a/apps/silcd/pubkey.pub and /dev/null differ diff --git a/apps/silcd/route.c b/apps/silcd/route.c index 43b60d38..eac09e64 100644 --- a/apps/silcd/route.c +++ b/apps/silcd/route.c @@ -22,16 +22,16 @@ * routes. If route entry doesn't exist for a specific destination, server * uses primary route (default route). */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* XXX Adding two ID's with same IP number replaces the old entry thus + gives wrong route. Thus, now disabled until figured out a better way + to do this or when removed the whole thing. This could be removed + because entry->router->connection gives always the most optimal route + for the ID anyway (unless new routes (faster perhaps) are established + after receiving this ID, this we don't know however). */ +/* $Id$ */ #include "serverincludes.h" +#include "server_internal.h" #include "route.h" /* Route cache hash table */ @@ -40,8 +40,8 @@ SilcServerRouteTable silc_route_cache[SILC_SERVER_ROUTE_SIZE]; /* Adds new route to the route cache. The argument `index' is the index value generated by silc_server_route_hash. */ -void silc_server_route_add(unsigned int index, unsigned int dest, - SilcServerList *router) +void silc_server_route_add(uint32 index, unsigned int dest, + SilcServerEntry router) { silc_route_cache[index].dest = dest; silc_route_cache[index].router = router; @@ -50,10 +50,10 @@ void silc_server_route_add(unsigned int index, unsigned int dest, /* Checksk whether destination has a specific router. Returns the router data if found, NULL otherwise. */ -SilcServerList *silc_server_route_check(unsigned int dest, - unsigned short port) +SilcServerEntry silc_server_route_check(uint32 dest, + uint16 port) { - unsigned int index; + uint32 index; index = silc_server_route_hash(dest, port); @@ -63,3 +63,45 @@ SilcServerList *silc_server_route_check(unsigned int dest, return NULL; } + +/* Returns the connection object for the fastest route for the given ID. + If we are normal server then this just returns our primary route. If + we are router we will do route lookup. */ + +SilcSocketConnection silc_server_route_get(SilcServer server, void *id, + SilcIdType id_type) +{ + if (server->server_type == SILC_ROUTER) { + uint32 dest = 0; + uint16 port = 0; + SilcServerEntry router = NULL; +#if 0 + + switch(id_type) { + case SILC_ID_CLIENT: + dest = ((SilcClientID *)id)->ip.s_addr; + port = server->id->port; + break; + case SILC_ID_SERVER: + dest = ((SilcServerID *)id)->ip.s_addr; + port = ((SilcServerID *)id)->port; + break; + case SILC_ID_CHANNEL: + dest = ((SilcChannelID *)id)->ip.s_addr; + port = ((SilcChannelID *)id)->port; + break; + default: + return NULL; + } + +#endif + + router = silc_server_route_check(dest, port); + if (!router) + return (SilcSocketConnection)server->id_entry->router->connection; + + return (SilcSocketConnection)router->connection; + } + + return (SilcSocketConnection)server->id_entry->router->connection; +} diff --git a/apps/silcd/route.h b/apps/silcd/route.h index aec76e69..077831d6 100644 --- a/apps/silcd/route.h +++ b/apps/silcd/route.h @@ -31,19 +31,19 @@ Following short description of the fields. - unsigned int dest + uint32 dest Destination IPv4 address. Can be used to quickly check whether the found route entry is what the caller wanted. - SilcServerList *router + SilcServerEntry router Pointer to the router specific data. */ typedef struct { - unsigned int dest; - SilcServerList *router; + uint32 dest; + SilcServerEntry router; } SilcServerRouteTable; /* Route cache hash table */ @@ -55,10 +55,10 @@ extern SilcServerRouteTable silc_route_cache[SILC_SERVER_ROUTE_SIZE]; `port' argument may be zero (0) if it doesn't exist. This has been taken from Linux kernel's route cache code. */ extern inline -unsigned int silc_server_route_hash(unsigned int addr, - unsigned short port) +uint32 silc_server_route_hash(unsigned int addr, + uint16 port) { - unsigned int hash; + uint32 hash; hash = ((addr & 0xf0f0f0f0) >> 4) | ((addr & 0x0f0f0f0f) << 4); hash ^= port; @@ -69,9 +69,11 @@ unsigned int silc_server_route_hash(unsigned int addr, } /* Prototypes */ -void silc_server_route_add(unsigned int index, unsigned int dest, - SilcServerList *router); -SilcServerList *silc_server_route_check(unsigned int dest, - unsigned short port); +void silc_server_route_add(uint32 index, unsigned int dest, + SilcServerEntry router); +SilcServerEntry silc_server_route_check(uint32 dest, + uint16 port); +SilcSocketConnection silc_server_route_get(SilcServer server, void *id, + SilcIdType id_type); #endif diff --git a/apps/silcd/server.c b/apps/silcd/server.c index 636b428f..31ae8877 100644 --- a/apps/silcd/server.c +++ b/apps/silcd/server.c @@ -2,9 +2,9 @@ server.c - Author: Pekka Riikonen + Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -22,19 +22,13 @@ * servicing the SILC connections. This is also a SILC router as a router * is also normal server. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "serverincludes.h" #include "server_internal.h" /* Static prototypes */ +SILC_TASK_CALLBACK(silc_server_connect_router); SILC_TASK_CALLBACK(silc_server_connect_to_router); SILC_TASK_CALLBACK(silc_server_connect_to_router_second); SILC_TASK_CALLBACK(silc_server_connect_to_router_final); @@ -42,25 +36,10 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection); SILC_TASK_CALLBACK(silc_server_accept_new_connection_second); SILC_TASK_CALLBACK(silc_server_accept_new_connection_final); SILC_TASK_CALLBACK(silc_server_packet_process); -SILC_TASK_CALLBACK(silc_server_packet_parse); +SILC_TASK_CALLBACK(silc_server_packet_parse_real); SILC_TASK_CALLBACK(silc_server_timeout_remote); - -/* XXX */ -void silc_server_packet_parse_type(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet); - -static int silc_server_packet_check_mac(SilcServer server, - SilcSocketConnection sock, - SilcBuffer buffer); -static int silc_server_packet_decrypt_rest(SilcServer server, - SilcSocketConnection sock, - SilcBuffer buffer); -static int silc_server_packet_decrypt_rest_special(SilcServer server, - SilcSocketConnection sock, - SilcBuffer buffer); - -extern char server_version[]; +SILC_TASK_CALLBACK(silc_server_failure_callback); +SILC_TASK_CALLBACK(silc_server_rekey_callback); /* Allocates a new SILC server object. This has to be done before the server can be used. After allocation one must call silc_server_init to initialize @@ -69,27 +48,21 @@ extern char server_version[]; int silc_server_alloc(SilcServer *new_server) { + SilcServer server; + SILC_LOG_DEBUG(("Allocating new server object")); - *new_server = silc_calloc(1, sizeof(**new_server)); - if (*new_server == NULL) { - SILC_LOG_ERROR(("Could not allocate new server object")); - return FALSE; - } + server = silc_calloc(1, sizeof(*server)); + server->server_type = SILC_SERVER; + server->standalone = TRUE; + server->local_list = silc_calloc(1, sizeof(*server->local_list)); + server->global_list = silc_calloc(1, sizeof(*server->global_list)); + server->pending_commands = silc_dlist_init(); +#ifdef SILC_SIM + server->sim = silc_dlist_init(); +#endif - /* Set default values */ - (*new_server)->server_name = NULL; - (*new_server)->server_type = SILC_SERVER; - (*new_server)->standalone = FALSE; - (*new_server)->id = NULL; - (*new_server)->io_queue = NULL; - (*new_server)->timeout_queue = NULL; - (*new_server)->local_list = silc_calloc(1, sizeof(SilcIDListObject)); - (*new_server)->global_list = silc_calloc(1, sizeof(SilcIDListObject)); - (*new_server)->rng = NULL; - (*new_server)->md5hash = NULL; - (*new_server)->sha1hash = NULL; - /* (*new_server)->public_key = NULL;*/ + *new_server = server; return TRUE; } @@ -100,14 +73,31 @@ int silc_server_alloc(SilcServer *new_server) void silc_server_free(SilcServer server) { if (server) { - if (server->local_list) - silc_free(server->local_list); - if (server->global_list) - silc_free(server->global_list); +#ifdef SILC_SIM + SilcSimContext *sim; +#endif + + silc_free(server->local_list); + silc_free(server->global_list); if (server->rng) silc_rng_free(server->rng); - silc_math_primegen_uninit(); /* XXX */ + if (server->pkcs) + silc_pkcs_free(server->pkcs); + +#ifdef SILC_SIM + while ((sim = silc_dlist_get(server->sim)) != SILC_LIST_END) { + silc_dlist_del(server->sim, sim); + silc_sim_free(sim); + } + silc_dlist_uninit(server->sim); +#endif + + silc_free(server->params); + + if (server->pending_commands) + silc_dlist_uninit(server->pending_commands); + silc_free(server); } } @@ -121,28 +111,54 @@ void silc_server_free(SilcServer server) int silc_server_init(SilcServer server) { - int *sock = NULL, sock_count, i; + int *sock = NULL, sock_count = 0, i; SilcServerID *id; - SilcServerList *id_entry; - SilcHashObject hash; + SilcServerEntry id_entry; + SilcIDListPurge purge; + SilcServerConfigSectionListenPort *listen; SILC_LOG_DEBUG(("Initializing server")); assert(server); assert(server->config); + /* Set public and private keys */ + if (!server->config->server_keys || + !server->config->server_keys->public_key || + !server->config->server_keys->private_key) { + SILC_LOG_ERROR(("Server public key and/or private key does not exist")); + return FALSE; + } + server->public_key = server->config->server_keys->public_key; + server->private_key = server->config->server_keys->private_key; + + /* XXX After server is made as Silc Server Library this can be given + as argument, for now this is hard coded */ + server->params = silc_calloc(1, sizeof(*server->params)); + server->params->retry_count = SILC_SERVER_RETRY_COUNT; + server->params->retry_interval_min = SILC_SERVER_RETRY_INTERVAL_MIN; + server->params->retry_interval_max = SILC_SERVER_RETRY_INTERVAL_MAX; + server->params->retry_keep_trying = FALSE; + server->params->protocol_timeout = 60; + server->params->require_reverse_mapping = FALSE; + /* Set log files where log message should be saved. */ server->config->server = server; - silc_config_server_setlogfiles(server->config); + silc_server_config_setlogfiles(server->config); /* Register all configured ciphers, PKCS and hash functions. */ - silc_config_server_register_ciphers(server->config); - silc_config_server_register_pkcs(server->config); - silc_config_server_register_hashfuncs(server->config); + if (!silc_server_config_register_ciphers(server->config)) + silc_cipher_register_default(); + if (!silc_server_config_register_pkcs(server->config)) + silc_pkcs_register_default(); + if (!silc_server_config_register_hashfuncs(server->config)) + silc_hash_register_default(); + if (!silc_server_config_register_hmacs(server->config)) + silc_hmac_register_default(); /* Initialize random number generator for the server. */ server->rng = silc_rng_alloc(); silc_rng_init(server->rng); - silc_math_primegen_init(); /* XXX */ + silc_rng_global_init(server->rng); /* Initialize hash functions for server to use */ silc_hash_alloc("md5", &server->md5hash); @@ -151,61 +167,47 @@ int silc_server_init(SilcServer server) /* Initialize none cipher */ silc_cipher_alloc("none", &server->none_cipher); - /* XXXXX Generate RSA key pair */ - { - unsigned char *public_key; - unsigned char *private_key; - unsigned int pk_len, prv_len; - - if (silc_pkcs_alloc("rsa", &server->public_key) == FALSE) { - SILC_LOG_ERROR(("Could not create RSA key pair")); - goto err0; - } - - if (server->public_key->pkcs->init(server->public_key->context, - 1024, server->rng) == FALSE) { - SILC_LOG_ERROR(("Could not generate RSA key pair")); - goto err0; - } - - public_key = - server->public_key->pkcs->get_public_key(server->public_key->context, - &pk_len); - private_key = - server->public_key->pkcs->get_private_key(server->public_key->context, - &prv_len); + /* Allocate PKCS context for local public and private keys */ + silc_pkcs_alloc(server->public_key->name, &server->pkcs); + silc_pkcs_public_key_set(server->pkcs, server->public_key); + silc_pkcs_private_key_set(server->pkcs, server->private_key); - SILC_LOG_HEXDUMP(("public key"), public_key, pk_len); - SILC_LOG_HEXDUMP(("private key"), private_key, prv_len); - - /* XXX Save keys */ - silc_pkcs_save_public_key(server->public_key, "pubkey.pub", - public_key, pk_len); - - memset(public_key, 0, pk_len); - memset(private_key, 0, prv_len); - silc_free(public_key); - silc_free(private_key); - } - - /* Create a listening server. Note that our server can listen on - multiple ports. All listeners are created here and now. */ - /* XXX Still check this whether to use server_info or listen_port. */ + /* Create a listening server. Note that our server can listen on multiple + ports. All listeners are created here and now. */ sock_count = 0; - while(server->config->listen_port) { + listen = server->config->listen_port; + while(listen) { int tmp; tmp = silc_net_create_server(server->config->listen_port->port, - server->config->listen_port->host); - if (tmp < 0) + server->config->listen_port->listener_ip); + if (tmp < 0) { + SILC_LOG_ERROR(("Could not create server listener: %s on %d", + server->config->listen_port->listener_ip, + server->config->listen_port->port)); goto err0; + } sock = silc_realloc(sock, (sizeof(int *) * (sock_count + 1))); sock[sock_count] = tmp; - server->config->listen_port = server->config->listen_port->next; sock_count++; + listen = listen->next; } + /* Initialize ID caches */ + server->local_list->clients = + silc_idcache_alloc(0, SILC_ID_CLIENT, silc_idlist_client_destructor); + server->local_list->servers = silc_idcache_alloc(0, SILC_ID_SERVER, NULL); + server->local_list->channels = silc_idcache_alloc(0, SILC_ID_CHANNEL, NULL); + + /* These are allocated for normal server as well as these hold some + global information that the server has fetched from its router. For + router these are used as they are supposed to be used on router. */ + server->global_list->clients = + silc_idcache_alloc(0, SILC_ID_CLIENT, silc_idlist_client_destructor); + server->global_list->servers = silc_idcache_alloc(0, SILC_ID_SERVER, NULL); + server->global_list->channels = silc_idcache_alloc(0, SILC_ID_CHANNEL, NULL); + /* Allocate the entire socket list that is used in server. Eventually all connections will have entry in this table (it is a table of pointers to the actual object that is allocated individually @@ -227,6 +229,8 @@ int silc_server_init(SilcServer server) } server->id = id; + server->id_string = silc_id_id2str(id, SILC_ID_SERVER); + server->id_string_len = silc_id_get_len(id, SILC_ID_SERVER); server->id_type = SILC_ID_SERVER; server->server_name = server->config->server_info->server_name; @@ -234,22 +238,39 @@ int silc_server_init(SilcServer server) beacuse we haven't established a route yet. It will be done later. For now, NULL is sent as router. This allocates new entry to the ID list. */ - silc_idlist_add_server(&server->local_list->servers, - server->config->server_info->server_name, - server->server_type, server->id, NULL, - server->send_key, server->receive_key, - NULL, NULL, &id_entry); - if (!id_entry) + id_entry = + silc_idlist_add_server(server->local_list, + server->config->server_info->server_name, + server->server_type, server->id, NULL, NULL); + if (!id_entry) { + SILC_LOG_ERROR(("Could not add ourselves to cache")); goto err0; + } + id_entry->data.status |= SILC_IDLIST_STATUS_REGISTERED; /* Add ourselves also to the socket table. The entry allocated above is sent as argument for fast referencing in the future. */ silc_socket_alloc(sock[i], SILC_SOCKET_TYPE_SERVER, id_entry, &newsocket); - if (!newsocket) - goto err0; server->sockets[sock[i]] = newsocket; + + /* Perform name and address lookups to resolve the listenning address + and port. */ + if (!silc_net_check_local_by_sock(sock[i], &newsocket->hostname, + &newsocket->ip)) { + if ((server->params->require_reverse_mapping && !newsocket->hostname) || + !newsocket->ip) { + SILC_LOG_ERROR(("IP/DNS lookup failed for local host %s", + newsocket->hostname ? newsocket->hostname : + newsocket->ip ? newsocket->ip : "")); + server->stat.conn_failures++; + goto err0; + } + if (!newsocket->hostname) + newsocket->hostname = strdup(newsocket->ip); + } + newsocket->port = silc_net_get_local_port(sock[i]); /* Put the allocated socket pointer also to the entry allocated above for fast back-referencing to the socket list. */ @@ -257,54 +278,77 @@ int silc_server_init(SilcServer server) server->id_entry = id_entry; } - /* Register the task queues. In SILC we have by default three task queues. - One task queue for non-timeout tasks which perform different kind of - I/O on file descriptors, timeout task queue for timeout tasks, and, - generic non-timeout task queue whose tasks apply to all connections. */ - silc_task_queue_alloc(&server->io_queue, TRUE); - if (!server->io_queue) { - goto err0; - } - silc_task_queue_alloc(&server->timeout_queue, TRUE); - if (!server->timeout_queue) { - goto err1; - } - silc_task_queue_alloc(&server->generic_queue, TRUE); - if (!server->generic_queue) { - goto err1; - } + /* Register protocols */ + silc_server_protocols_register(); - /* Initialize the scheduler */ - silc_schedule_init(server->io_queue, server->timeout_queue, - server->generic_queue, - SILC_SERVER_MAX_CONNECTIONS); + /* Initialize the scheduler. */ + server->schedule = silc_schedule_init(SILC_SERVER_MAX_CONNECTIONS); + if (!server->schedule) + goto err0; - /* Add the first task to the queue. This is task that is executed by + /* Add the first task to the scheduler. This is task that is executed by timeout. It expires as soon as the caller calls silc_server_run. This task performs authentication protocol and key exchange with our primary router. */ - if (silc_task_register(server->timeout_queue, sock[0], + silc_schedule_task_add(server->schedule, sock[0], silc_server_connect_to_router, (void *)server, 0, 1, SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL) == NULL) { - goto err2; - } + SILC_TASK_PRI_NORMAL); + + /* Add listener task to the scheduler. This task receives new connections + to the server. This task remains on the queue until the end of the + program. */ + silc_schedule_task_add(server->schedule, sock[0], + silc_server_accept_new_connection, + (void *)server, 0, 0, + SILC_TASK_FD, + SILC_TASK_PRI_NORMAL); + server->listenning = TRUE; /* If server connections has been configured then we must be router as normal server cannot have server connections, only router connections. */ - if (server->config->servers) + if (server->config->servers) { + SilcServerConfigSectionServerConnection *ptr = server->config->servers; + server->server_type = SILC_ROUTER; + while (ptr) { + if (ptr->backup_router) { + server->server_type = SILC_BACKUP_ROUTER; + server->backup_router = TRUE; + server->id_entry->server_type = SILC_BACKUP_ROUTER; + break; + } + ptr = ptr->next; + } + } + + /* Register the ID Cache purge task. This periodically purges the ID cache + and removes the expired cache entries. */ + + /* Clients local list */ + purge = silc_calloc(1, sizeof(*purge)); + purge->cache = server->local_list->clients; + purge->schedule = server->schedule; + silc_schedule_task_add(purge->schedule, 0, + silc_idlist_purge, + (void *)purge, 600, 0, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + + /* Clients global list */ + purge = silc_calloc(1, sizeof(*purge)); + purge->cache = server->global_list->clients; + purge->schedule = server->schedule; + silc_schedule_task_add(purge->schedule, 0, + silc_idlist_purge, + (void *)purge, 300, 0, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); SILC_LOG_DEBUG(("Server initialized")); /* We are done here, return succesfully */ return TRUE; - err2: - silc_task_queue_free(server->timeout_queue); - err1: - silc_task_queue_free(server->io_queue); err0: for (i = 0; i < sock_count; i++) silc_net_close_server(sock[i]); @@ -312,6 +356,125 @@ int silc_server_init(SilcServer server) return FALSE; } +/* Fork server to background and set gid+uid to non-root. + Silcd will not run as root, so trying to set either user or group to + root will cause silcd to exit. */ + +void silc_server_daemonise(SilcServer server) +{ + /* Are we executing silcd as root or a regular user? */ + if (geteuid()==0) { + + struct passwd *pw; + struct group *gr; + char *user, *group; + + if (!server->config->identity || !server->config->identity->user || + !server->config->identity->group) { + fprintf(stderr, "Error:" + "\tSILC server must not be run as root. For the security of your\n" + "\tsystem it is strongly suggested that you run SILC under dedicated\n" + "\tuser account. Modify the [Identity] configuration section to run\n" + "\tthe server as non-root user.\n"); + exit(1); + } + + /* Get the values given for user and group in configuration file */ + user=server->config->identity->user; + group=server->config->identity->group; + + /* Check whether the user/group information is text */ + if (atoi(user)!=0 || atoi(group)!=0) { + SILC_LOG_DEBUG(("Invalid user and/or group information")); + SILC_LOG_DEBUG(("User and/or group given as number")); + fprintf(stderr, "Invalid user and/or group information\n"); + fprintf(stderr, "Please assign them as names, not numbers\n"); + exit(1); + } + + /* Catch the nasty incident of string "0" returning 0 from atoi */ + if (strcmp("0", user)==0 || strcmp("0", group)==0) { + SILC_LOG_DEBUG(("User and/or group configured to 0. Unacceptable")); + fprintf(stderr, "User and/or group configured to 0. Exiting\n"); + exit(1); + } + + pw=getpwnam(user); + gr=getgrnam(group); + + if (!pw) { + fprintf(stderr, "No such user %s found\n", user); + exit(1); + } + + if (!gr) { + fprintf(stderr, "No such group %s found\n", group); + exit(1); + } + + /* Check whether user and/or group is set to root. If yes, exit + immediately. Otherwise, setgid and setuid server to user.group */ + if (gr->gr_gid==0 || pw->pw_uid==0) { + fprintf(stderr, "Error:" + "\tSILC server must not be run as root. For the security of your\n" + "\tsystem it is strongly suggested that you run SILC under dedicated\n" + "\tuser account. Modify the [Identity] configuration section to run\n" + "\tthe server as non-root user.\n"); + exit(1); + } else { + /* Fork server to background, making it a daemon */ + if (fork()) { + SILC_LOG_DEBUG(("Server started as root. Dropping privileges.")); + SILC_LOG_DEBUG(("Forking SILC server to background")); + exit(0); + } + setsid(); + + SILC_LOG_DEBUG(("Changing to group %s", group)); + if(setgid(gr->gr_gid)==0) { + SILC_LOG_DEBUG(("Setgid to %s", group)); + } else { + SILC_LOG_DEBUG(("Setgid to %s failed", group)); + fprintf(stderr, "Tried to setgid %s but no such group. Exiting\n", + group); + exit(1); + } + SILC_LOG_DEBUG(("Changing to user nobody")); + if(setuid(pw->pw_uid)==0) { + SILC_LOG_DEBUG(("Setuid to %s", user)); + } else { + SILC_LOG_DEBUG(("Setuid to %s failed", user)); + fprintf(stderr, "Tried to setuid %s but no such user. Exiting\n", + user); + exit(1); + } + } + } else { + /* Fork server to background, making it a daemon */ + if (fork()) { + SILC_LOG_DEBUG(("Server started as user")); + SILC_LOG_DEBUG(("Forking SILC server to background")); + exit(0); + } + setsid(); + } +} + +/* The heart of the server. This runs the scheduler thus runs the server. + When this returns the server has been stopped and the program will + be terminated. */ + +void silc_server_run(SilcServer server) +{ + SILC_LOG_DEBUG(("Running server")); + + SILC_LOG_INFO(("SILC Server started")); + + /* Start the scheduler, the heart of the SILC server. When this returns + the program will be terminated. */ + silc_schedule(server->schedule); +} + /* Stops the SILC server. This function is used to shutdown the server. This is usually called after the scheduler has returned. After stopping the server one should call silc_server_free. */ @@ -320,26 +483,150 @@ void silc_server_stop(SilcServer server) { SILC_LOG_DEBUG(("Stopping server")); - /* 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_schedule_stop(server->schedule); + silc_schedule_uninit(server->schedule); + + silc_server_protocols_unregister(); SILC_LOG_DEBUG(("Server stopped")); } -/* The heart of the server. This runs the scheduler thus runs the server. */ +/* Function that is called when the network connection to a router has + been established. This will continue with the key exchange protocol + with the remote router. */ -void silc_server_run(SilcServer server) +void silc_server_start_key_exchange(SilcServer server, + SilcServerConnection sconn, + int sock) { - SILC_LOG_DEBUG(("Running server")); + SilcSocketConnection newsocket; + SilcProtocol protocol; + SilcServerKEInternalContext *proto_ctx; + void *context; - /* Start the scheduler, the heart of the SILC server. When this returns - the program will be terminated. */ - silc_schedule(); + /* Set socket options */ + silc_net_set_socket_nonblock(sock); + silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + + /* Create socket connection for the connection. Even though we + know that we are connecting to a router we will mark the socket + to be unknown connection until we have executed authentication + protocol. */ + silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket); + server->sockets[sock] = newsocket; + newsocket->hostname = strdup(sconn->remote_host); + newsocket->ip = strdup(sconn->remote_host); + newsocket->port = sconn->remote_port; + sconn->sock = newsocket; + + /* Allocate internal protocol context. This is sent as context + to the protocol. */ + proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); + proto_ctx->server = (void *)server; + proto_ctx->context = (void *)sconn; + proto_ctx->sock = newsocket; + proto_ctx->rng = server->rng; + proto_ctx->responder = FALSE; + + /* Perform key exchange protocol. silc_server_connect_to_router_second + will be called after the protocol is finished. */ + silc_protocol_alloc(SILC_PROTOCOL_SERVER_KEY_EXCHANGE, + &protocol, proto_ctx, + silc_server_connect_to_router_second); + newsocket->protocol = protocol; + + /* Register a timeout task that will be executed if the protocol + is not executed within set limit. */ + proto_ctx->timeout_task = + silc_schedule_task_add(server->schedule, sock, + silc_server_timeout_remote, + server, server->params->protocol_timeout, + server->params->protocol_timeout_usec, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_LOW); + + /* 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_SET_CONNECTION_FOR_OUTPUT, + later when outgoing data is available. */ + context = (void *)server; + SILC_REGISTER_CONNECTION_FOR_IO(sock); + + /* Run the protocol */ + silc_protocol_execute(protocol, server->schedule, 0, 0); +} + +/* Timeout callback that will be called to retry connecting to remote + router. This is used by both normal and router server. This will wait + before retrying the connecting. The timeout is generated by exponential + backoff algorithm. */ + +SILC_TASK_CALLBACK(silc_server_connect_to_router_retry) +{ + SilcServerConnection sconn = (SilcServerConnection)context; + SilcServer server = sconn->server; + + SILC_LOG_INFO(("Retrying connecting to a router")); + + /* Calculate next timeout */ + if (sconn->retry_count >= 1) { + sconn->retry_timeout = sconn->retry_timeout * SILC_SERVER_RETRY_MULTIPLIER; + if (sconn->retry_timeout > SILC_SERVER_RETRY_INTERVAL_MAX) + sconn->retry_timeout = SILC_SERVER_RETRY_INTERVAL_MAX; + } else { + sconn->retry_timeout = server->params->retry_interval_min; + } + sconn->retry_count++; + sconn->retry_timeout = sconn->retry_timeout + + silc_rng_get_rn32(server->rng) % SILC_SERVER_RETRY_RANDOMIZER; + + /* If we've reached max retry count, give up. */ + if (sconn->retry_count > server->params->retry_count && + server->params->retry_keep_trying == FALSE) { + SILC_LOG_ERROR(("Could not connect to router, giving up")); + return; + } + + /* Wait one before retrying */ + silc_schedule_task_add(server->schedule, fd, silc_server_connect_router, + context, sconn->retry_timeout, + server->params->retry_interval_min_usec, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); } +/* Generic routine to use connect to a router. */ + +SILC_TASK_CALLBACK(silc_server_connect_router) +{ + SilcServerConnection sconn = (SilcServerConnection)context; + SilcServer server = sconn->server; + int sock; + + SILC_LOG_INFO(("Connecting to the %s %s on port %d", + (sconn->backup ? "backup router" : "router"), + sconn->remote_host, sconn->remote_port)); + + server->router_connect = time(0); + + /* Connect to remote host */ + sock = silc_net_create_connection(server->config->listen_port->local_ip, + sconn->remote_port, + sconn->remote_host); + if (sock < 0) { + SILC_LOG_ERROR(("Could not connect to router")); + silc_schedule_task_add(server->schedule, fd, + silc_server_connect_to_router_retry, + context, 0, 1, SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); + return; + } + + /* Continue with key exchange protocol */ + silc_server_start_key_exchange(server, sconn, sock); +} + /* This function connects to our primary router or if we are a router this establishes all our primary routes. This is called at the start of the server to do authentication and key exchange with our router - called @@ -348,154 +635,50 @@ void silc_server_run(SilcServer server) SILC_TASK_CALLBACK(silc_server_connect_to_router) { SilcServer server = (SilcServer)context; - SilcSocketConnection newsocket; - int sock; + SilcServerConnection sconn; + SilcServerConfigSectionServerConnection *ptr; SILC_LOG_DEBUG(("Connecting to router(s)")); - /* if we are normal SILC server we need to connect to our cell's - router. */ if (server->server_type == SILC_SERVER) { - SilcProtocol protocol; - SilcServerKEInternalContext *proto_ctx; - - /* Create connection to the router, if configured. */ - if (server->config->routers) { - sock = silc_net_create_connection(server->config->routers->port, - server->config->routers->host); - if (sock < 0) { - SILC_LOG_ERROR(("Could not connect to router")); - silc_schedule_stop(); - return; - } - - /* Set socket options */ - silc_net_set_socket_nonblock(sock); - silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - - /* Create socket connection for the connection. Even though we - know that we are connecting to a router we will mark the socket - to be unknown connection until we have executed authentication - protocol. */ - silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket); - server->sockets[sock] = newsocket; - newsocket->hostname = server->config->routers->host; - newsocket->port = server->config->routers->port; - - /* Allocate internal protocol context. This is sent as context - to the protocol. */ - proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); - proto_ctx->server = context; - proto_ctx->sock = newsocket; - proto_ctx->rng = server->rng; - proto_ctx->responder = FALSE; - - /* Perform key exchange protocol. silc_server_connect_to_router_second - will be called after the protocol is finished. */ - silc_protocol_alloc(SILC_PROTOCOL_SERVER_KEY_EXCHANGE, - &protocol, proto_ctx, - silc_server_connect_to_router_second); - newsocket->protocol = protocol; - - /* Register a timeout task that will be executed if the protocol - is not executed within 15 seconds. For now, this is a hard coded - limit. After 15 secs the connection will be closed if the key - exchange protocol has not been executed. */ - proto_ctx->timeout_task = - silc_task_register(server->timeout_queue, sock, - silc_server_timeout_remote, - context, 15, 0, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_LOW); - - /* 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_SET_CONNECTION_FOR_OUTPUT, - later when outgoing data is available. */ - SILC_REGISTER_CONNECTION_FOR_IO(sock); - - /* Run the protocol */ - protocol->execute(server->timeout_queue, 0, protocol, sock, 0, 0); - return; - } + SILC_LOG_DEBUG(("We are normal server")); + } else if (server->server_type == SILC_ROUTER) { + SILC_LOG_DEBUG(("We are router")); + } else { + SILC_LOG_DEBUG(("We are backup router/normal server")); } - - /* if we are a SILC router we need to establish all of our primary - routes. */ - if (server->server_type == SILC_ROUTER) { - SilcConfigServerSectionServerConnection *ptr; - /* Create the connections to all our routes */ - ptr = server->config->routers; - while (ptr) { - SilcProtocol protocol; - SilcServerKEInternalContext *proto_ctx; - - /* Create the connection to the remote end */ - sock = silc_net_create_connection(ptr->port, ptr->host); - if (sock < 0) { - SILC_LOG_ERROR(("Could not connect to router")); - silc_schedule_stop(); - return; + /* Create the connections to all our routes */ + ptr = server->config->routers; + while (ptr) { + + SILC_LOG_DEBUG(("%s connection [%s] %s:%d", + ptr->backup_router ? "Backup router" : "Router", + ptr->initiator ? "Initiator" : "Responder", + ptr->host, ptr->port)); + + if (ptr->initiator) { + /* Allocate connection object for hold connection specific stuff. */ + sconn = silc_calloc(1, sizeof(*sconn)); + sconn->server = server; + sconn->remote_host = strdup(ptr->host); + sconn->remote_port = ptr->port; + sconn->backup = ptr->backup_router; + if (sconn->backup) { + sconn->backup_replace_ip = strdup(ptr->backup_replace_ip); + sconn->backup_replace_port = ptr->backup_replace_port; } - /* Set socket options */ - silc_net_set_socket_nonblock(sock); - silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - - /* Create socket connection for the connection. Even though we - know that we are connecting to a router we will mark the socket - to be unknown connection until we have executed authentication - protocol. */ - silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket); - server->sockets[sock] = newsocket; - newsocket->hostname = ptr->host; - newsocket->port = ptr->port; - - /* Allocate internal protocol context. This is sent as context - to the protocol. */ - proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); - proto_ctx->server = context; - proto_ctx->sock = newsocket; - proto_ctx->rng = server->rng; - proto_ctx->responder = FALSE; - - /* Perform key exchange protocol. silc_server_connect_to_router_final - will be called after the protocol is finished. */ - silc_protocol_alloc(SILC_PROTOCOL_SERVER_KEY_EXCHANGE, - &protocol, proto_ctx, - silc_server_connect_to_router_second); - newsocket->protocol = protocol; - - /* Register a timeout task that will be executed if the protocol - is not executed within 15 seconds. For now, this is a hard coded - limit. After 15 secs the connection will be closed if the key - exchange protocol has not been executed. */ - proto_ctx->timeout_task = - silc_task_register(server->timeout_queue, sock, - silc_server_timeout_remote, - context, 15, 0, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_LOW); - - /* 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_SET_CONNECTION_FOR_OUTPUT, - later when outgoing data is available. */ - SILC_REGISTER_CONNECTION_FOR_IO(sock); - - /* Run the protocol */ - protocol->execute(server->timeout_queue, 0, protocol, sock, 0, 0); - - if (!ptr->next) - return; - - ptr = ptr->next; + silc_schedule_task_add(server->schedule, fd, + silc_server_connect_router, + (void *)sconn, 0, 1, SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); } + + if (!ptr->next) + return; + + ptr = ptr->next; } SILC_LOG_DEBUG(("No router(s), server will be standalone")); @@ -503,17 +686,6 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router) /* There wasn't a configured router, we will continue but we don't have a connection to outside world. We will be standalone server. */ server->standalone = TRUE; - - /* Add a task to the queue. This task receives new connections to the - server. This task remains on the queue until the end of the program. */ - if (silc_task_register(server->io_queue, fd, - silc_server_accept_new_connection, - (void *)server, 0, 0, - SILC_TASK_FD, - SILC_TASK_PRI_NORMAL) == NULL) { - silc_schedule_stop(); - return; - } } /* Second part of connecting to router(s). Key exchange protocol has been @@ -525,64 +697,103 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_second) SilcServerKEInternalContext *ctx = (SilcServerKEInternalContext *)protocol->context; SilcServer server = (SilcServer)ctx->server; - SilcSocketConnection sock = NULL; + SilcServerConnection sconn = (SilcServerConnection)ctx->context; + SilcSocketConnection sock = ctx->sock; SilcServerConnAuthInternalContext *proto_ctx; + SilcServerConfigSectionServerConnection *conn = NULL; SILC_LOG_DEBUG(("Start")); - if (protocol->state == SILC_PROTOCOL_STATE_ERROR) { + if (protocol->state == SILC_PROTOCOL_STATE_ERROR || + protocol->state == SILC_PROTOCOL_STATE_FAILURE) { /* Error occured during protocol */ silc_protocol_free(protocol); + sock->protocol = NULL; + silc_ske_free_key_material(ctx->keymat); if (ctx->packet) - silc_buffer_free(ctx->packet); + silc_packet_context_free(ctx->packet); if (ctx->ske) silc_ske_free(ctx->ske); - if (ctx->dest_id) - silc_free(ctx->dest_id); + silc_free(ctx->dest_id); silc_free(ctx); - sock->protocol = NULL; + silc_schedule_task_del_by_callback(server->schedule, + silc_server_failure_callback); silc_server_disconnect_remote(server, sock, "Server closed connection: " "Key exchange failed"); return; } + /* We now have the key material as the result of the key exchange + protocol. Take the key material into use. Free the raw key material + as soon as we've set them into use. */ + if (!silc_server_protocol_ke_set_keys(ctx->ske, ctx->sock, ctx->keymat, + ctx->ske->prop->cipher, + ctx->ske->prop->pkcs, + ctx->ske->prop->hash, + ctx->ske->prop->hmac, + ctx->ske->prop->group, + ctx->responder)) { + silc_protocol_free(protocol); + sock->protocol = NULL; + silc_ske_free_key_material(ctx->keymat); + if (ctx->packet) + silc_packet_context_free(ctx->packet); + if (ctx->ske) + silc_ske_free(ctx->ske); + silc_free(ctx->dest_id); + silc_free(ctx); + silc_schedule_task_del_by_callback(server->schedule, + silc_server_failure_callback); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Key exchange failed"); + return; + } + silc_ske_free_key_material(ctx->keymat); + /* 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->server = (void *)server; - proto_ctx->sock = sock = server->sockets[fd]; + proto_ctx->context = (void *)sconn; + proto_ctx->sock = 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 used in this connection */ - proto_ctx->auth_meth = SILC_PROTOCOL_CONN_AUTH_PASSWORD; - if (server->config->routers) { - SilcConfigServerSectionServerConnection *conn = NULL; - - /* Check if we find a match from user configured connections */ - conn = silc_config_server_find_router_conn(server->config, - sock->hostname, - sock->port); - if (conn) { - /* Match found. Use the configured authentication method */ - proto_ctx->auth_meth = conn->auth_meth; - if (conn->auth_data) { - proto_ctx->auth_data = strdup(conn->auth_data); - proto_ctx->auth_data_len = strlen(conn->auth_data); - } - } else { - /* No match found. */ - /* XXX */ + /* Resolve the authentication method used in this connection. Check if + we find a match from user configured connections */ + conn = silc_server_config_find_router_conn(server->config, + sock->hostname, + sock->port); + if (conn) { + /* Match found. Use the configured authentication method */ + proto_ctx->auth_meth = conn->auth_meth; + if (conn->auth_data) { + proto_ctx->auth_data = strdup(conn->auth_data); + proto_ctx->auth_data_len = strlen(conn->auth_data); } } else { - /* XXX */ + SILC_LOG_ERROR(("Could not find connection data for %s (%s) on port", + sock->hostname, sock->ip, sock->port)); + silc_protocol_free(protocol); + sock->protocol = NULL; + if (ctx->packet) + silc_packet_context_free(ctx->packet); + if (ctx->ske) + silc_ske_free(ctx->ske); + silc_free(ctx->dest_id); + silc_free(ctx); + silc_schedule_task_del_by_callback(server->schedule, + silc_server_failure_callback); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Key exchange failed"); + return; } /* Free old protocol as it is finished now */ silc_protocol_free(protocol); if (ctx->packet) - silc_buffer_free(ctx->packet); + silc_packet_context_free(ctx->packet); silc_free(ctx); sock->protocol = NULL; @@ -598,15 +809,14 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_second) this timelimit the connection will be terminated. Currently this is 15 seconds and is hard coded limit (XXX). */ proto_ctx->timeout_task = - silc_task_register(server->timeout_queue, sock->sock, + silc_schedule_task_add(server->schedule, sock->sock, silc_server_timeout_remote, (void *)server, 15, 0, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); /* Run the protocol */ - sock->protocol->execute(server->timeout_queue, 0, - sock->protocol, sock->sock, 0, 0); + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); } /* Finalizes the connection to router. Registers a server task to the @@ -618,54 +828,46 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_final) SilcServerConnAuthInternalContext *ctx = (SilcServerConnAuthInternalContext *)protocol->context; SilcServer server = (SilcServer)ctx->server; + SilcServerConnection sconn = (SilcServerConnection)ctx->context; SilcSocketConnection sock = ctx->sock; - SilcServerList *id_entry; - SilcIDListUnknown *conn_data; + SilcServerEntry id_entry; SilcBuffer packet; + SilcServerHBContext hb_context; unsigned char *id_string; + uint32 id_len; + SilcIDListData idata; SILC_LOG_DEBUG(("Start")); - if (protocol->state == SILC_PROTOCOL_STATE_ERROR) { + if (protocol->state == SILC_PROTOCOL_STATE_ERROR || + protocol->state == SILC_PROTOCOL_STATE_FAILURE) { /* Error occured during protocol */ - silc_protocol_free(protocol); - if (ctx->packet) - silc_buffer_free(ctx->packet); - if (ctx->ske) - silc_ske_free(ctx->ske); - if (ctx->dest_id) - silc_free(ctx->dest_id); - silc_free(ctx); - sock->protocol = NULL; + silc_free(ctx->dest_id); silc_server_disconnect_remote(server, sock, "Server closed connection: " "Authentication failed"); - return; + goto out; } /* Add a task to the queue. This task receives new connections to the server. This task remains on the queue until the end of the program. */ - if (!server->listenning) { - if (silc_task_register(server->io_queue, server->sock, + if (!server->listenning && !sconn->backup) { + silc_schedule_task_add(server->schedule, server->sock, silc_server_accept_new_connection, (void *)server, 0, 0, SILC_TASK_FD, - SILC_TASK_PRI_NORMAL) == NULL) { - silc_schedule_stop(); - return; - } else { - server->listenning = TRUE; - } + SILC_TASK_PRI_NORMAL); + server->listenning = TRUE; } /* Send NEW_SERVER packet to the router. We will become registered to the SILC network after sending this packet. */ id_string = silc_id_id2str(server->id, SILC_ID_SERVER); - packet = silc_buffer_alloc(2 + 2 + SILC_ID_SERVER_LEN + - strlen(server->server_name)); + id_len = silc_id_get_len(server->id, SILC_ID_SERVER); + packet = silc_buffer_alloc(2 + 2 + id_len + strlen(server->server_name)); silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); silc_buffer_format(packet, - SILC_STR_UI_SHORT(SILC_ID_SERVER_LEN), - SILC_STR_UI_XNSTRING(id_string, SILC_ID_SERVER_LEN), + SILC_STR_UI_SHORT(id_len), + SILC_STR_UI_XNSTRING(id_string, id_len), SILC_STR_UI_SHORT(strlen(server->server_name)), SILC_STR_UI_XNSTRING(server->server_name, strlen(server->server_name)), @@ -677,120 +879,268 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_final) silc_buffer_free(packet); silc_free(id_string); - SILC_LOG_DEBUG(("Connected to router %s", sock->hostname)); + SILC_LOG_INFO(("Connected to router %s", sock->hostname)); + + /* Check that we do not have this ID already */ + id_entry = silc_idlist_find_server_by_id(server->local_list, + ctx->dest_id, TRUE, NULL); + if (id_entry) { + silc_idcache_del_by_context(server->local_list->servers, id_entry); + } else { + id_entry = silc_idlist_find_server_by_id(server->global_list, + ctx->dest_id, TRUE, NULL); + if (id_entry) + silc_idcache_del_by_context(server->global_list->servers, id_entry); + } + + SILC_LOG_DEBUG(("New server id(%s)", + silc_id_render(ctx->dest_id, SILC_ID_SERVER))); - /* Add the connected router to local server list */ - server->standalone = FALSE; - conn_data = (SilcIDListUnknown *)sock->user_data; - silc_idlist_add_server(&server->local_list->servers, - sock->hostname ? sock->hostname : sock->ip, - SILC_ROUTER, ctx->dest_id, NULL, - conn_data->send_key, conn_data->receive_key, - conn_data->pkcs, conn_data->hmac, &id_entry); + /* Add the connected router to global server list */ + id_entry = silc_idlist_add_server(server->global_list, + strdup(sock->hostname), + SILC_ROUTER, ctx->dest_id, NULL, sock); + if (!id_entry) { + silc_free(ctx->dest_id); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Authentication failed"); + goto out; + } - id_entry->hmac_key = conn_data->hmac_key; - id_entry->hmac_key_len = conn_data->hmac_key_len; - id_entry->connection = sock; + silc_idlist_add_data(id_entry, (SilcIDListData)sock->user_data); + silc_free(sock->user_data); sock->user_data = (void *)id_entry; sock->type = SILC_SOCKET_TYPE_ROUTER; - server->id_entry->router = id_entry; + idata = (SilcIDListData)sock->user_data; + idata->status |= SILC_IDLIST_STATUS_REGISTERED; + + /* Perform keepalive. The `hb_context' will be freed automatically + when finally calling the silc_socket_free function. XXX hardcoded + timeout!! */ + hb_context = silc_calloc(1, sizeof(*hb_context)); + hb_context->server = server; + silc_socket_set_heartbeat(sock, 600, hb_context, + silc_server_perform_heartbeat, + server->schedule); + + /* Register re-key timeout */ + idata->rekey->timeout = 3600; /* XXX hardcoded */ + idata->rekey->context = (void *)server; + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_rekey_callback, + (void *)sock, idata->rekey->timeout, 0, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); + + if (!sconn->backup) { + /* Mark this router our primary router if we're still standalone */ + if (server->standalone) { + server->id_entry->router = id_entry; + server->router = id_entry; + server->standalone = FALSE; + + /* If we are router then announce our possible servers. */ + if (server->server_type == SILC_ROUTER) + silc_server_announce_servers(server, FALSE, 0, + server->router->connection); + + /* Announce our clients and channels to the router */ + silc_server_announce_clients(server, 0, server->router->connection); + silc_server_announce_channels(server, 0, server->router->connection); + } + } else { + /* Add this server to be our backup router */ + silc_server_backup_add(server, id_entry, sconn->backup_replace_ip, + sconn->backup_replace_port, FALSE); + } - /* Free the temporary connection data context from key exchange */ - silc_free(conn_data); + sock->protocol = NULL; - /* Free the protocol object */ + /* Call the completion callback to indicate that we've connected to + the router */ + if (sconn->callback) + (*sconn->callback)(server, id_entry, sconn->callback_context); + + out: + /* Free the temporary connection data context */ + if (sconn) { + silc_free(sconn->remote_host); + silc_free(sconn->backup_replace_ip); + silc_free(sconn); + } + + /* Free the protocol object */ + if (sock->protocol == protocol) + sock->protocol = NULL; silc_protocol_free(protocol); if (ctx->packet) - silc_buffer_free(ctx->packet); + silc_packet_context_free(ctx->packet); if (ctx->ske) silc_ske_free(ctx->ske); silc_free(ctx); - sock->protocol = NULL; } -/* Accepts new connections to the server. Accepting new connections are - done in three parts to make it async. */ +/* Host lookup callbcak that is called after the incoming connection's + IP and FQDN lookup is performed. This will actually check the acceptance + of the incoming connection and will register the key exchange protocol + for this connection. */ -SILC_TASK_CALLBACK(silc_server_accept_new_connection) +static void +silc_server_accept_new_connection_lookup(SilcSocketConnection sock, + void *context) { SilcServer server = (SilcServer)context; - SilcSocketConnection newsocket; SilcServerKEInternalContext *proto_ctx; - int sock; + void *cconfig, *sconfig, *rconfig; + SilcServerConfigSectionDenyConnection *deny; + int port; - SILC_LOG_DEBUG(("Accepting new connection")); + SILC_LOG_DEBUG(("Start")); - sock = silc_net_accept_connection(server->sock); - if (sock < 0) { - SILC_LOG_ERROR(("Could not accept new connection: %s", strerror(errno))); + /* Check whether we could resolve both IP and FQDN. */ + if (!sock->ip || (!strcmp(sock->ip, sock->hostname) && + server->params->require_reverse_mapping)) { + SILC_LOG_ERROR(("IP/DNS lookup failed %s", + sock->hostname ? sock->hostname : + sock->ip ? sock->ip : "")); + server->stat.conn_failures++; + silc_server_disconnect_remote(server, sock, + "Server closed connection: Unknown host"); return; } - /* Check max connections */ - if (sock > SILC_SERVER_MAX_CONNECTIONS) { - if (server->config->redirect) { - /* XXX Redirecting connection to somewhere else now?? */ - /*silc_server_send_notify("Server is full, trying to redirect..."); */ - } else { - SILC_LOG_ERROR(("Refusing connection, server is full")); - } + /* 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_SET_CONNECTION_FOR_OUTPUT, + later when outgoing data is available. */ + SILC_REGISTER_CONNECTION_FOR_IO(sock->sock); + + SILC_LOG_INFO(("Incoming connection from %s (%s)", sock->hostname, + sock->ip)); + + port = server->sockets[server->sock]->port; /* Listenning port */ + + /* Check whether this connection is denied to connect to us. */ + deny = silc_server_config_denied_conn(server->config, sock->ip, port); + if (!deny) + deny = silc_server_config_denied_conn(server->config, sock->hostname, + port); + if (deny) { + /* The connection is denied */ + SILC_LOG_INFO(("Connection %s (%s) is denied", + sock->hostname, sock->ip)); + silc_server_disconnect_remote(server, sock, deny->comment ? + deny->comment : + "Server closed connection: " + "Connection refused"); + server->stat.conn_failures++; return; } - /* Set socket options */ - silc_net_set_socket_nonblock(sock); - silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - - /* We don't create a ID yet, since we don't know what type of connection - this is yet. But, we do add the connection to the socket table. */ - silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket); - server->sockets[sock] = newsocket; - - /* XXX This MUST be done async as this will block the entire server - process. Either we have to do our own resolver stuff or in the future - we can use threads. */ - /* Perform mandatory name and address lookups for the remote host. */ - silc_net_check_host_by_sock(sock, &newsocket->hostname, &newsocket->ip); - if (!newsocket->ip || !newsocket->hostname) { - SILC_LOG_DEBUG(("IP lookup/DNS lookup failed")); - SILC_LOG_ERROR(("IP lookup/DNS lookup failed")); + /* Check whether we have configred this sort of connection at all. We + have to check all configurations since we don't know what type of + connection this is. */ + if (!(cconfig = silc_server_config_find_client_conn(server->config, + sock->ip, port))) + cconfig = silc_server_config_find_client_conn(server->config, + sock->hostname, + port); + if (!(sconfig = silc_server_config_find_server_conn(server->config, + sock->ip, + port))) + sconfig = silc_server_config_find_server_conn(server->config, + sock->hostname, + port); + if (!(rconfig = silc_server_config_find_router_conn(server->config, + sock->ip, port))) + rconfig = silc_server_config_find_router_conn(server->config, + sock->hostname, + port); + if (!cconfig && !sconfig && !rconfig) { + silc_server_disconnect_remote(server, sock, + "Server closed connection: " + "Connection refused"); + server->stat.conn_failures++; return; } + /* The connection is allowed */ + /* Allocate internal context for key exchange protocol. This is sent as context for the protocol. */ proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); proto_ctx->server = context; - proto_ctx->sock = newsocket; + proto_ctx->sock = sock; proto_ctx->rng = server->rng; proto_ctx->responder = TRUE; + proto_ctx->cconfig = cconfig; + proto_ctx->sconfig = sconfig; + proto_ctx->rconfig = rconfig; /* Prepare the connection for key exchange protocol. We allocate the protocol but will not start it yet. The connector will be the initiator of the protocol thus we will wait for initiation from there before we start the protocol. */ + server->stat.auth_attempts++; silc_protocol_alloc(SILC_PROTOCOL_SERVER_KEY_EXCHANGE, - &newsocket->protocol, proto_ctx, + &sock->protocol, proto_ctx, silc_server_accept_new_connection_second); /* Register a timeout task that will be executed if the connector - will not start the key exchange protocol within 15 seconds. For - now, this is a hard coded limit. After 15 secs the connection will + will not start the key exchange protocol within 60 seconds. For + now, this is a hard coded limit. After 60 secs the connection will be closed if the key exchange protocol has not been started. */ proto_ctx->timeout_task = - silc_task_register(server->timeout_queue, newsocket->sock, - silc_server_timeout_remote, - context, 15, 0, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_LOW); + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_timeout_remote, + context, 60, 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_LOW); +} - /* 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_SET_CONNECTION_FOR_OUTPUT, - later when outgoing data is available. */ - SILC_REGISTER_CONNECTION_FOR_IO(sock); +/* Accepts new connections to the server. Accepting new connections are + done in three parts to make it async. */ + +SILC_TASK_CALLBACK(silc_server_accept_new_connection) +{ + SilcServer server = (SilcServer)context; + SilcSocketConnection newsocket; + int sock; + + SILC_LOG_DEBUG(("Accepting new connection")); + + server->stat.conn_attempts++; + + sock = silc_net_accept_connection(server->sock); + if (sock < 0) { + SILC_LOG_ERROR(("Could not accept new connection: %s", strerror(errno))); + server->stat.conn_failures++; + return; + } + + /* Check max connections */ + if (sock > SILC_SERVER_MAX_CONNECTIONS) { + SILC_LOG_ERROR(("Refusing connection, server is full")); + server->stat.conn_failures++; + return; + } + + /* Set socket options */ + silc_net_set_socket_nonblock(sock); + silc_net_set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + + /* We don't create a ID yet, since we don't know what type of connection + this is yet. But, we do add the connection to the socket table. */ + silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket); + server->sockets[sock] = newsocket; + + /* Perform asynchronous host lookup. This will lookup the IP and the + FQDN of the remote connection. After the lookup is done the connection + is accepted further. */ + silc_socket_host_lookup(newsocket, TRUE, + silc_server_accept_new_connection_lookup, context, + server->schedule); } /* Second part of accepting new connection. Key exchange protocol has been @@ -805,41 +1155,76 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_second) SilcServerKEInternalContext *ctx = (SilcServerKEInternalContext *)protocol->context; SilcServer server = (SilcServer)ctx->server; - SilcSocketConnection sock = NULL; + SilcSocketConnection sock = ctx->sock; SilcServerConnAuthInternalContext *proto_ctx; SILC_LOG_DEBUG(("Start")); - if (protocol->state == SILC_PROTOCOL_STATE_ERROR) { + if (protocol->state == SILC_PROTOCOL_STATE_ERROR || + protocol->state == SILC_PROTOCOL_STATE_FAILURE) { /* Error occured during protocol */ silc_protocol_free(protocol); + sock->protocol = NULL; + silc_ske_free_key_material(ctx->keymat); if (ctx->packet) - silc_buffer_free(ctx->packet); + silc_packet_context_free(ctx->packet); if (ctx->ske) silc_ske_free(ctx->ske); - if (ctx->dest_id) - silc_free(ctx->dest_id); + silc_free(ctx->dest_id); silc_free(ctx); - sock->protocol = NULL; + silc_schedule_task_del_by_callback(server->schedule, + silc_server_failure_callback); silc_server_disconnect_remote(server, sock, "Server closed connection: " "Key exchange failed"); + server->stat.auth_failures++; return; } + /* We now have the key material as the result of the key exchange + protocol. Take the key material into use. Free the raw key material + as soon as we've set them into use. */ + if (!silc_server_protocol_ke_set_keys(ctx->ske, ctx->sock, ctx->keymat, + ctx->ske->prop->cipher, + ctx->ske->prop->pkcs, + ctx->ske->prop->hash, + ctx->ske->prop->hmac, + ctx->ske->prop->group, + ctx->responder)) { + silc_protocol_free(protocol); + sock->protocol = NULL; + silc_ske_free_key_material(ctx->keymat); + if (ctx->packet) + silc_packet_context_free(ctx->packet); + if (ctx->ske) + silc_ske_free(ctx->ske); + silc_free(ctx->dest_id); + silc_free(ctx); + silc_schedule_task_del_by_callback(server->schedule, + silc_server_failure_callback); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Key exchange failed"); + server->stat.auth_failures++; + return; + } + silc_ske_free_key_material(ctx->keymat); + /* 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->server = (void *)server; - proto_ctx->sock = sock = server->sockets[fd]; + proto_ctx->sock = sock; proto_ctx->ske = ctx->ske; /* Save SKE object from previous protocol */ proto_ctx->responder = TRUE; proto_ctx->dest_id_type = ctx->dest_id_type; proto_ctx->dest_id = ctx->dest_id; + proto_ctx->cconfig = ctx->cconfig; + proto_ctx->sconfig = ctx->sconfig; + proto_ctx->rconfig = ctx->rconfig; /* Free old protocol as it is finished now */ silc_protocol_free(protocol); if (ctx->packet) - silc_buffer_free(ctx->packet); + silc_packet_context_free(ctx->packet); silc_free(ctx); sock->protocol = NULL; @@ -855,11 +1240,11 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_second) this timelimit the connection will be terminated. Currently this is 60 seconds and is hard coded limit (XXX). */ proto_ctx->timeout_task = - silc_task_register(server->timeout_queue, sock->sock, - silc_server_timeout_remote, - (void *)server, 60, 0, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_LOW); + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_timeout_remote, + (void *)server, 60, 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_LOW); } /* Final part of accepting new connection. The connection has now @@ -873,120 +1258,188 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final) (SilcServerConnAuthInternalContext *)protocol->context; SilcServer server = (SilcServer)ctx->server; SilcSocketConnection sock = ctx->sock; + SilcServerHBContext hb_context; + SilcUnknownEntry entry = (SilcUnknownEntry)sock->user_data; + void *id_entry = NULL; SILC_LOG_DEBUG(("Start")); - if (protocol->state == SILC_PROTOCOL_STATE_ERROR) { + if (protocol->state == SILC_PROTOCOL_STATE_ERROR || + protocol->state == SILC_PROTOCOL_STATE_FAILURE) { /* Error occured during protocol */ silc_protocol_free(protocol); + sock->protocol = NULL; if (ctx->packet) - silc_buffer_free(ctx->packet); + silc_packet_context_free(ctx->packet); if (ctx->ske) silc_ske_free(ctx->ske); - if (ctx->dest_id) - silc_free(ctx->dest_id); + silc_free(ctx->dest_id); silc_free(ctx); - sock->protocol = NULL; + silc_schedule_task_del_by_callback(server->schedule, + silc_server_failure_callback); silc_server_disconnect_remote(server, sock, "Server closed connection: " "Authentication failed"); + server->stat.auth_failures++; return; } - sock->type = ctx->conn_type; - switch(sock->type) { + entry->data.last_receive = time(NULL); + + switch (ctx->conn_type) { case SILC_SOCKET_TYPE_CLIENT: { - SilcClientList *id_entry = NULL; - SilcIDListUnknown *conn_data = sock->user_data; + SilcClientEntry client; SILC_LOG_DEBUG(("Remote host is client")); + SILC_LOG_INFO(("Connection from %s (%s) is client", sock->hostname, + sock->ip)); + + /* Add the client to the client ID cache. The nickname and Client ID + and other information is created after we have received NEW_CLIENT + packet from client. */ + client = silc_idlist_add_client(server->local_list, + NULL, NULL, NULL, NULL, NULL, sock); + if (!client) { + SILC_LOG_ERROR(("Could not add new client to cache")); + silc_free(sock->user_data); + silc_server_disconnect_remote(server, sock, + "Server closed connection: " + "Authentication failed"); + server->stat.auth_failures++; + goto out; + } - /* Add the client to the client ID list. We have not created the - client ID for the client yet. This is done when client registers - itself by sending NEW_CLIENT packet. */ - silc_idlist_add_client(&server->local_list->clients, - NULL, NULL, NULL, NULL, NULL, - conn_data->send_key, conn_data->receive_key, - conn_data->pkcs, conn_data->hmac, &id_entry); - - id_entry->hmac_key = conn_data->hmac_key; - id_entry->hmac_key_len = conn_data->hmac_key_len; - id_entry->connection = sock; - - /* Free the temporary connection data context from key exchange */ - silc_free(conn_data); + /* Statistics */ + server->stat.my_clients++; + server->stat.clients++; + if (server->server_type == SILC_ROUTER) + server->stat.cell_clients++; - /* Mark the entry to the ID list to the socket connection for - fast referencing in the future. */ - sock->user_data = (void *)id_entry; + id_entry = (void *)client; break; } case SILC_SOCKET_TYPE_SERVER: case SILC_SOCKET_TYPE_ROUTER: { - SilcServerList *id_entry; - SilcIDListUnknown *conn_data = sock->user_data; - + SilcServerEntry new_server; + SilcServerConfigSectionServerConnection *conn = + ctx->conn_type == SILC_SOCKET_TYPE_SERVER ? + ctx->sconfig : ctx->rconfig; + SILC_LOG_DEBUG(("Remote host is %s", - sock->type == SILC_SOCKET_TYPE_SERVER ? - "server" : "router")); - - /* Add the server to the ID list. We don't have the server's ID - yet but we will receive it after the server sends NEW_SERVER - packet to us. */ - silc_idlist_add_server(&server->local_list->servers, NULL, - sock->type == SILC_SOCKET_TYPE_SERVER ? - SILC_SERVER : SILC_ROUTER, NULL, NULL, - conn_data->send_key, conn_data->receive_key, - conn_data->pkcs, conn_data->hmac, &id_entry); - - id_entry->hmac_key = conn_data->hmac_key; - id_entry->hmac_key_len = conn_data->hmac_key_len; - id_entry->connection = sock; - - /* Free the temporary connection data context from key exchange */ - silc_free(conn_data); - - /* Mark the entry to the ID list to the socket connection for - fast referencing in the future. */ - sock->user_data = (void *)id_entry; - - /* There is connection to other server now, if it is router then - we will have connection to outside world. If we are router but - normal server connected to us then we will remain standalone, - if we are standlone. */ - if (server->standalone && sock->type == SILC_SOCKET_TYPE_ROUTER) { + ctx->conn_type == SILC_SOCKET_TYPE_SERVER ? + "server" : (conn->backup_router ? + "backup router" : "router"))); + SILC_LOG_INFO(("Connection from %s (%s) is %s", sock->hostname, + sock->ip, ctx->conn_type == SILC_SOCKET_TYPE_SERVER ? + "server" : (conn->backup_router ? + "backup router" : "router"))); + + /* Add the server into server cache. The server name and Server ID + is updated after we have received NEW_SERVER packet from the + server. We mark ourselves as router for this server if we really + are router. */ + new_server = + silc_idlist_add_server((ctx->conn_type == SILC_SOCKET_TYPE_SERVER ? + server->local_list : (conn->backup_router ? + server->local_list : + server->global_list)), + NULL, + (ctx->conn_type == SILC_SOCKET_TYPE_SERVER ? + SILC_SERVER : SILC_ROUTER), + NULL, + (ctx->conn_type == SILC_SOCKET_TYPE_SERVER ? + server->id_entry : (conn->backup_router ? + server->id_entry : NULL)), + sock); + if (!new_server) { + SILC_LOG_ERROR(("Could not add new server to cache")); + silc_free(sock->user_data); + silc_server_disconnect_remote(server, sock, + "Server closed connection: " + "Authentication failed"); + server->stat.auth_failures++; + goto out; + } + + /* Statistics */ + if (ctx->conn_type == SILC_SOCKET_TYPE_SERVER) + server->stat.my_servers++; + else + server->stat.my_routers++; + server->stat.servers++; + + id_entry = (void *)new_server; + + /* If the incoming connection is router and marked as backup router + then add it to be one of our backups */ + if (ctx->conn_type == SILC_SOCKET_TYPE_ROUTER && conn->backup_router) { + silc_server_backup_add(server, new_server, conn->backup_replace_ip, + conn->backup_replace_port, conn->backup_local); + + /* Change it back to SERVER type since that's what it really is. */ + if (conn->backup_local) + ctx->conn_type = SILC_SOCKET_TYPE_SERVER; + + new_server->server_type = SILC_BACKUP_ROUTER; + } + + /* Check whether this connection is to be our primary router connection + if we do not already have the primary route. */ + if (server->standalone && ctx->conn_type == SILC_SOCKET_TYPE_ROUTER) { + if (silc_server_config_is_primary_route(server->config) && + !conn->initiator) + break; + SILC_LOG_DEBUG(("We are not standalone server anymore")); server->standalone = FALSE; + if (!server->id_entry->router) { + server->id_entry->router = id_entry; + server->router = id_entry; + } } + break; } default: break; } + sock->type = ctx->conn_type; + + /* Add the common data structure to the ID entry. */ + if (id_entry) + silc_idlist_add_data(id_entry, (SilcIDListData)sock->user_data); + + /* Add to sockets internal pointer for fast referencing */ + silc_free(sock->user_data); + sock->user_data = id_entry; + /* Connection has been fully established now. Everything is ok. */ SILC_LOG_DEBUG(("New connection authenticated")); + /* Perform keepalive. The `hb_context' will be freed automatically + when finally calling the silc_socket_free function. XXX hardcoded + timeout!! */ + hb_context = silc_calloc(1, sizeof(*hb_context)); + hb_context->server = server; + silc_socket_set_heartbeat(sock, 600, hb_context, + silc_server_perform_heartbeat, + server->schedule); + + out: + silc_schedule_task_del_by_callback(server->schedule, + silc_server_failure_callback); silc_protocol_free(protocol); if (ctx->packet) - silc_buffer_free(ctx->packet); + silc_packet_context_free(ctx->packet); if (ctx->ske) silc_ske_free(ctx->ske); - if (ctx->dest_id) - silc_free(ctx->dest_id); + silc_free(ctx->dest_id); silc_free(ctx); sock->protocol = NULL; } -typedef struct { - SilcPacketContext *packetdata; - SilcServer server; - SilcSocketConnection sock; - SilcCipher cipher; - SilcHmac hmac; -} SilcServerInternalPacket; - /* This function is used to read packets from network and send packets to network. This is usually a generic task. */ @@ -994,35 +1447,52 @@ SILC_TASK_CALLBACK(silc_server_packet_process) { SilcServer server = (SilcServer)context; SilcSocketConnection sock = server->sockets[fd]; - int ret, packetlen, paddedlen; + SilcIDListData idata; + SilcCipher cipher = NULL; + SilcHmac hmac = NULL; + uint32 sequence = 0; + int ret; + + if (!sock) + return; SILC_LOG_DEBUG(("Processing packet")); /* Packet sending */ + if (type == SILC_TASK_WRITE) { - SILC_LOG_DEBUG(("Writing data to connection")); + /* Do not send data to disconnected connection */ + if (SILC_IS_DISCONNECTED(sock)) + return; + + server->stat.packets_sent++; if (sock->outbuf->data - sock->outbuf->head) - silc_buffer_push(sock->outbuf, - sock->outbuf->data - sock->outbuf->head); + silc_buffer_push(sock->outbuf, sock->outbuf->data - sock->outbuf->head); - /* Write the packet out to the connection */ - ret = silc_packet_write(fd, sock->outbuf); + /* Send the packet */ + ret = silc_packet_send(sock, TRUE); /* If returned -2 could not write to connection now, will do it later. */ if (ret == -2) return; - - /* Error */ - if (ret == -1) - SILC_LOG_ERROR(("Could not write, packet dropped")); + if (ret == -1) { + SILC_LOG_ERROR(("Error sending packet to connection " + "%s:%d [%s]", sock->hostname, sock->port, + (sock->type == SILC_SOCKET_TYPE_UNKNOWN ? "Unknown" : + sock->type == SILC_SOCKET_TYPE_CLIENT ? "Client" : + sock->type == SILC_SOCKET_TYPE_SERVER ? "Server" : + "Router"))); + 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_SET_CONNECTION_FOR_INPUT(fd); + SILC_SET_CONNECTION_FOR_INPUT(server->schedule, fd); SILC_UNSET_OUTBUF_PENDING(sock); silc_buffer_clear(sock->outbuf); @@ -1030,556 +1500,236 @@ SILC_TASK_CALLBACK(silc_server_packet_process) } /* Packet receiving */ - if (type == SILC_TASK_READ) { - SILC_LOG_DEBUG(("Reading data from connection")); - /* Allocate the incoming data buffer if not done already. */ - if (!sock->inbuf) - sock->inbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE); + /* Read some data from connection */ + ret = silc_packet_receive(sock); + if (ret < 0) { - /* Read some data from connection */ - ret = silc_packet_read(fd, sock->inbuf); - - /* If returned -2 data was not available now, will read it later. */ - if (ret == -2) - return; - - /* Error */ - if (ret == -1) { - SILC_LOG_ERROR(("Could not read, packet dropped")); - 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)) { - if (sock->user_data) - silc_server_free_sock_user_data(server, sock); - silc_server_close_connection(server, sock); - return; - } - - SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock)); + if (ret == -1) + SILC_LOG_ERROR(("Error receiving packet from connection " + "%s:%d [%s]", sock->hostname, sock->port, + (sock->type == SILC_SOCKET_TYPE_UNKNOWN ? "Unknown" : + sock->type == SILC_SOCKET_TYPE_CLIENT ? "Client" : + sock->type == SILC_SOCKET_TYPE_SERVER ? "Server" : + "Router"))); + 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)) { if (sock->user_data) - silc_server_free_sock_user_data(server, sock); + silc_server_free_sock_user_data(server, sock); silc_server_close_connection(server, sock); return; } - - /* If connection is disconnecting or disconnected we will ignore - what we read. */ - if (SILC_IS_DISCONNECTING(sock) || SILC_IS_DISCONNECTED(sock)) { - SILC_LOG_DEBUG(("Ignoring read data from invalid connection")); - return; - } - - /* Check whether we received a whole packet. If reading went without - errors we either read a whole packet or the read packet is - incorrect and will be dropped. */ - SILC_PACKET_LENGTH(sock->inbuf, packetlen, paddedlen); - if (sock->inbuf->len < paddedlen || (packetlen < SILC_PACKET_MIN_LEN)) { - SILC_LOG_DEBUG(("Received incorrect packet, dropped")); - silc_buffer_clear(sock->inbuf); - silc_server_disconnect_remote(server, sock, "Incorrect packet"); - return; - } - - /* Decrypt a packet coming from client. */ - if (sock->type == SILC_SOCKET_TYPE_CLIENT) { - SilcClientList *clnt = (SilcClientList *)sock->user_data; - SilcServerInternalPacket *packet; - int mac_len = 0; - if (clnt->hmac) - mac_len = clnt->hmac->hash->hash->hash_len; - - if (sock->inbuf->len - 2 > (paddedlen + mac_len)) { - /* Received possibly many packets at once */ - - while(sock->inbuf->len > 0) { - SILC_PACKET_LENGTH(sock->inbuf, packetlen, paddedlen); - if (sock->inbuf->len < paddedlen) { - SILC_LOG_DEBUG(("Receive incorrect packet, dropped")); - return; - } + SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock)); + SILC_SET_DISCONNECTING(sock); - paddedlen += 2; - packet = silc_calloc(1, sizeof(*packet)); - packet->server = server; - packet->sock = sock; - packet->packetdata = silc_calloc(1, sizeof(*packet->packetdata)); - packet->packetdata->buffer = silc_buffer_alloc(paddedlen + mac_len); - silc_buffer_pull_tail(packet->packetdata->buffer, - SILC_BUFFER_END(packet->packetdata->buffer)); - silc_buffer_put(packet->packetdata->buffer, sock->inbuf->data, - paddedlen + mac_len); - if (clnt) { - packet->cipher = clnt->receive_key; - packet->hmac = clnt->hmac; - } - - SILC_LOG_HEXDUMP(("Incoming packet, len %d", - packet->packetdata->buffer->len), - packet->packetdata->buffer->data, - packet->packetdata->buffer->len); + if (sock->user_data) + silc_server_free_sock_user_data(server, sock); + silc_server_close_connection(server, sock); + return; + } - /* Parse the packet with timeout */ - silc_task_register(server->timeout_queue, fd, - silc_server_packet_parse, - (void *)packet, 0, 100000, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); + /* If connection is disconnecting or disconnected we will ignore + what we read. */ + if (SILC_IS_DISCONNECTING(sock) || SILC_IS_DISCONNECTED(sock)) { + SILC_LOG_DEBUG(("Ignoring read data from disonnected connection")); + return; + } - /* Pull the packet from inbuf thus we'll get the next one - in the inbuf. */ - silc_buffer_pull(sock->inbuf, paddedlen); - if (clnt->hmac) - silc_buffer_pull(sock->inbuf, mac_len); - } - silc_buffer_clear(sock->inbuf); - return; - } else { - SILC_LOG_HEXDUMP(("An incoming packet, len %d", sock->inbuf->len), - sock->inbuf->data, sock->inbuf->len); - - SILC_LOG_DEBUG(("Packet from client, length %d", paddedlen)); - - packet = silc_calloc(1, sizeof(*packet)); - packet->packetdata = silc_calloc(1, sizeof(*packet->packetdata)); - packet->packetdata->buffer = silc_buffer_copy(sock->inbuf); - packet->server = server; - packet->sock = sock; - if (clnt) { - packet->cipher = clnt->receive_key; - packet->hmac = clnt->hmac; - } - silc_buffer_clear(sock->inbuf); - - /* The packet is ready to be parsed now. However, this is a client - connection so we will parse the packet with timeout. */ - silc_task_register(server->timeout_queue, fd, - silc_server_packet_parse, - (void *)packet, 0, 100000, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); - return; - } - } - - /* Decrypt a packet coming from server connection */ - if (sock->type == SILC_SOCKET_TYPE_SERVER || - sock->type == SILC_SOCKET_TYPE_ROUTER) { - SilcServerList *srvr = (SilcServerList *)sock->user_data; - SilcServerInternalPacket *packet; - int mac_len = 0; - - if (srvr->hmac) - mac_len = srvr->hmac->hash->hash->hash_len; + server->stat.packets_received++; - if (sock->inbuf->len - 2 > (paddedlen + mac_len)) { - /* Received possibly many packets at once */ + /* Get keys and stuff from ID entry */ + idata = (SilcIDListData)sock->user_data; + if (idata) { + cipher = idata->receive_key; + hmac = idata->hmac_receive; + sequence = idata->psn_receive; + } + + /* Process the packet. This will call the parser that will then + decrypt and parse the packet. */ + silc_packet_receive_process(sock, server->server_type == SILC_ROUTER ? + TRUE : FALSE, cipher, hmac, sequence, + silc_server_packet_parse, server); +} + +/* Parses whole packet, received earlier. */ - while(sock->inbuf->len > 0) { - SILC_PACKET_LENGTH(sock->inbuf, packetlen, paddedlen); - if (sock->inbuf->len < paddedlen) { - SILC_LOG_DEBUG(("Received incorrect packet, dropped")); - return; - } +SILC_TASK_CALLBACK(silc_server_packet_parse_real) +{ + SilcPacketParserContext *parse_ctx = (SilcPacketParserContext *)context; + SilcServer server = (SilcServer)parse_ctx->context; + SilcSocketConnection sock = parse_ctx->sock; + SilcPacketContext *packet = parse_ctx->packet; + SilcIDListData idata = (SilcIDListData)sock->user_data; + int ret; - paddedlen += 2; - packet = silc_calloc(1, sizeof(*packet)); - packet->server = server; - packet->sock = sock; - packet->packetdata = silc_calloc(1, sizeof(*packet->packetdata)); - packet->packetdata->buffer = silc_buffer_alloc(paddedlen + mac_len); - silc_buffer_pull_tail(packet->packetdata->buffer, - SILC_BUFFER_END(packet->packetdata->buffer)); - silc_buffer_put(packet->packetdata->buffer, sock->inbuf->data, - paddedlen + mac_len); - if (srvr) { - packet->cipher = srvr->receive_key; - packet->hmac = srvr->hmac; - } + SILC_LOG_DEBUG(("Start")); - SILC_LOG_HEXDUMP(("Incoming packet, len %d", - packet->packetdata->buffer->len), - packet->packetdata->buffer->data, - packet->packetdata->buffer->len); + /* Parse the packet */ + if (parse_ctx->normal) + ret = silc_packet_parse(packet, idata ? idata->receive_key : NULL); + else + ret = silc_packet_parse_special(packet, idata ? idata->receive_key : NULL); - SILC_LOG_DEBUG(("Packet from %s %s, packet length %d", - srvr->server_type == SILC_SERVER ? - "server" : "router", - srvr->server_name, paddedlen)); - - /* Parse it real soon as the packet is from server. */ - silc_task_register(server->timeout_queue, fd, - silc_server_packet_parse, - (void *)packet, 0, 1, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); + /* If entry is disabled ignore what we got. */ + if (ret != SILC_PACKET_RESUME_ROUTER && + idata && idata->status & SILC_IDLIST_STATUS_DISABLED) { + SILC_LOG_DEBUG(("Connection is disabled")); + goto out; + } - /* Pull the packet from inbuf thus we'll get the next one - in the inbuf. */ - silc_buffer_pull(sock->inbuf, paddedlen); - if (srvr->hmac) - silc_buffer_pull(sock->inbuf, mac_len); - } - silc_buffer_clear(sock->inbuf); - return; - } else { + if (ret == SILC_PACKET_NONE) + goto out; - SILC_LOG_HEXDUMP(("An incoming packet, len %d", sock->inbuf->len), - sock->inbuf->data, sock->inbuf->len); - - SILC_LOG_DEBUG(("Packet from %s %s, packet length %d", - srvr->server_type == SILC_SERVER ? - "server" : "router", - srvr->server_name, paddedlen)); - - packet = silc_calloc(1, sizeof(*packet)); - packet->packetdata = silc_calloc(1, sizeof(*packet->packetdata)); - packet->packetdata->buffer = silc_buffer_copy(sock->inbuf); - packet->server = server; - packet->sock = sock; - if (srvr) { - packet->cipher = srvr->receive_key; - packet->hmac = srvr->hmac; - } - silc_buffer_clear(sock->inbuf); - - /* The packet is ready to be parsed now. However, this is a client - connection so we will parse the packet with timeout. */ - silc_task_register(server->timeout_queue, fd, - silc_server_packet_parse, - (void *)packet, 0, 1, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); - return; + /* Check that the the current client ID is same as in the client's packet. */ + if (sock->type == SILC_SOCKET_TYPE_CLIENT) { + SilcClientEntry client = (SilcClientEntry)sock->user_data; + if (client && client->id) { + void *id = silc_id_str2id(packet->src_id, packet->src_id_len, + packet->src_id_type); + if (!id || !SILC_ID_CLIENT_COMPARE(client->id, id)) { + silc_free(id); + goto out; } + silc_free(id); } + } - /* Decrypt a packet coming from client. */ - if (sock->type == SILC_SOCKET_TYPE_UNKNOWN) { - SilcIDListUnknown *conn_data = (SilcIDListUnknown *)sock->user_data; - SilcServerInternalPacket *packet; + if (server->server_type == SILC_ROUTER) { + /* Route the packet if it is not destined to us. Other ID types but + server are handled separately after processing them. */ + if (!(packet->flags & SILC_PACKET_FLAG_BROADCAST) && + packet->dst_id_type == SILC_ID_SERVER && + sock->type != SILC_SOCKET_TYPE_CLIENT && + memcmp(packet->dst_id, server->id_string, packet->dst_id_len)) { - SILC_LOG_HEXDUMP(("Incoming packet, len %d", sock->inbuf->len), - sock->inbuf->data, sock->inbuf->len); - - SILC_LOG_DEBUG(("Packet from unknown connection, length %d", - paddedlen)); - - packet = silc_calloc(1, sizeof(*packet)); - packet->packetdata = silc_calloc(1, sizeof(*packet->packetdata)); - packet->packetdata->buffer = silc_buffer_copy(sock->inbuf); - packet->server = server; - packet->sock = sock; - if (conn_data) { - packet->cipher = conn_data->receive_key; - packet->hmac = conn_data->hmac; - } + /* Route the packet to fastest route for the destination ID */ + void *id = silc_id_str2id(packet->dst_id, packet->dst_id_len, + packet->dst_id_type); + if (!id) + goto out; + silc_server_packet_route(server, + silc_server_route_get(server, id, + packet->dst_id_type), + packet); + silc_free(id); + goto out; + } + } - silc_buffer_clear(sock->inbuf); + /* Parse the incoming packet type */ + silc_server_packet_parse_type(server, sock, packet); - /* The packet is ready to be parsed now. However, this is unknown - connection so we will parse the packet with timeout. */ - silc_task_register(server->timeout_queue, fd, - silc_server_packet_parse, - (void *)packet, 0, 100000, - SILC_TASK_TIMEOUT, - SILC_TASK_PRI_NORMAL); - return; + if (server->server_type == SILC_ROUTER) { + /* Broadcast packet if it is marked as broadcast packet and it is + originated from router and we are router. */ + if (sock->type == SILC_SOCKET_TYPE_ROUTER && + packet->flags & SILC_PACKET_FLAG_BROADCAST && + !server->standalone) { + /* Broadcast to our primary route */ + silc_server_packet_broadcast(server, server->router->connection, packet); + + /* If we have backup routers then we need to feed all broadcast + data to those servers. */ + silc_server_backup_broadcast(server, sock, packet); } } - SILC_LOG_ERROR(("Weird, nothing happened - ignoring")); + out: + silc_packet_context_free(packet); + silc_free(parse_ctx); } -/* Checks MAC in the packet. Returns TRUE if MAC is Ok. This is called - after packet has been totally decrypted and parsed. */ +/* Parser callback called by silc_packet_receive_process. This merely + registers timeout that will handle the actual parsing when appropriate. */ -static int silc_server_packet_check_mac(SilcServer server, - SilcSocketConnection sock, - SilcBuffer buffer) +bool silc_server_packet_parse(SilcPacketParserContext *parser_context, + void *context) { - SilcHmac hmac = NULL; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned int mac_len = 0; + SilcServer server = (SilcServer)context; + SilcSocketConnection sock = parser_context->sock; + SilcIDListData idata = (SilcIDListData)sock->user_data; + + if (idata) + idata->psn_receive = parser_context->packet->sequence + 1; + + /* If protocol for this connection is key exchange or rekey then we'll + process all packets synchronously, since there might be packets in + queue that we are not able to decrypt without first processing the + packets before them. */ + if (sock->protocol && sock->protocol->protocol && + (sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_KEY_EXCHANGE || + sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_REKEY)) { + silc_server_packet_parse_real(server->schedule, 0, sock->sock, + parser_context); + + /* Reprocess data since we'll return FALSE here. This is because + the idata->receive_key might have become valid in the last packet + and we want to call this processor with valid cipher. */ + if (idata) + silc_packet_receive_process(sock, server->server_type == SILC_ROUTER ? + TRUE : FALSE, idata->receive_key, + idata->hmac_receive, idata->psn_receive, + silc_server_packet_parse, server); + else + silc_packet_receive_process(sock, server->server_type == SILC_ROUTER ? + TRUE : FALSE, NULL, NULL, 0, + silc_server_packet_parse, server); + return FALSE; + } - switch(sock->type) { + switch (sock->type) { + case SILC_SOCKET_TYPE_UNKNOWN: case SILC_SOCKET_TYPE_CLIENT: - if (sock->user_data) { - hmac = ((SilcClientList *)sock->user_data)->hmac; - hmac_key = ((SilcClientList *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcClientList *)sock->user_data)->hmac_key_len; - } + /* Parse the packet with timeout */ + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_packet_parse_real, + (void *)parser_context, 0, 100000, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); break; case SILC_SOCKET_TYPE_SERVER: case SILC_SOCKET_TYPE_ROUTER: - if (sock->user_data) { - hmac = ((SilcServerList *)sock->user_data)->hmac; - hmac_key = ((SilcServerList *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcServerList *)sock->user_data)->hmac_key_len; - } + /* Packets from servers are parsed immediately */ + silc_server_packet_parse_real(server->schedule, 0, sock->sock, + parser_context); break; default: - if (sock->user_data) { - hmac = ((SilcIDListUnknown *)sock->user_data)->hmac; - hmac_key = ((SilcIDListUnknown *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcIDListUnknown *)sock->user_data)->hmac_key_len; - } + return TRUE; } - /* Check MAC */ - if (hmac) { - int headlen = buffer->data - buffer->head; - unsigned char *packet_mac, mac[32]; - - SILC_LOG_DEBUG(("Verifying MAC")); + return TRUE; +} - mac_len = hmac->hash->hash->hash_len; - - silc_buffer_push(buffer, headlen); - - /* Take mac from packet */ - packet_mac = buffer->tail; - - /* Make MAC and compare */ - memset(mac, 0, sizeof(mac)); - silc_hmac_make_with_key(hmac, - buffer->data, buffer->len, - hmac_key, hmac_key_len, mac); -#if 0 - SILC_LOG_HEXDUMP(("PMAC"), packet_mac, mac_len); - SILC_LOG_HEXDUMP(("CMAC"), mac, mac_len); -#endif - if (memcmp(mac, packet_mac, mac_len)) { - SILC_LOG_DEBUG(("MAC failed")); - return FALSE; - } - - SILC_LOG_DEBUG(("MAC is Ok")); - memset(mac, 0, sizeof(mac)); - - silc_buffer_pull(buffer, headlen); - } - - return TRUE; -} - -/* Decrypts rest of the packet (after decrypting just the SILC header). - After calling this function the packet is ready to be parsed by calling - silc_packet_parse. */ - -static int silc_server_packet_decrypt_rest(SilcServer server, - SilcSocketConnection sock, - SilcBuffer buffer) -{ - SilcCipher session_key = NULL; - SilcHmac hmac = NULL; - unsigned int mac_len = 0; - - switch(sock->type) { - case SILC_SOCKET_TYPE_CLIENT: - if (sock->user_data) { - session_key = ((SilcClientList *)sock->user_data)->receive_key; - hmac = ((SilcClientList *)sock->user_data)->hmac; - } - break; - case SILC_SOCKET_TYPE_SERVER: - case SILC_SOCKET_TYPE_ROUTER: - if (sock->user_data) { - session_key = ((SilcServerList *)sock->user_data)->receive_key; - hmac = ((SilcServerList *)sock->user_data)->hmac; - } - break; - default: - if (sock->user_data) { - session_key = ((SilcIDListUnknown *)sock->user_data)->receive_key; - hmac = ((SilcIDListUnknown *)sock->user_data)->hmac; - } - } - - /* Decrypt */ - if (session_key) { - - /* Pull MAC from packet before decryption */ - if (hmac) { - mac_len = hmac->hash->hash->hash_len; - if ((buffer->len - mac_len) > SILC_PACKET_MIN_LEN) { - silc_buffer_push_tail(buffer, mac_len); - } else { - SILC_LOG_DEBUG(("Bad MAC length in packet, packet dropped")); - return FALSE; - } - } - - SILC_LOG_DEBUG(("Decrypting rest of the packet")); - - /* Decrypt rest of the packet */ - silc_buffer_pull(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - silc_packet_decrypt(session_key, buffer, buffer->len); - silc_buffer_push(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - - SILC_LOG_HEXDUMP(("Fully decrypted packet, len %d", buffer->len), - buffer->data, buffer->len); - } - - return TRUE; -} - -/* Decrypts rest of the SILC Packet header that has been decrypted partly - already. This decrypts the padding of the packet also. After calling - this function the packet is ready to be parsed by calling function - silc_packet_parse. */ - -static int silc_server_packet_decrypt_rest_special(SilcServer server, - SilcSocketConnection sock, - SilcBuffer buffer) -{ - SilcCipher session_key = NULL; - SilcHmac hmac = NULL; - unsigned int mac_len = 0; - - switch(sock->type) { - case SILC_SOCKET_TYPE_CLIENT: - if (sock->user_data) { - session_key = ((SilcClientList *)sock->user_data)->receive_key; - hmac = ((SilcClientList *)sock->user_data)->hmac; - } - break; - case SILC_SOCKET_TYPE_SERVER: - case SILC_SOCKET_TYPE_ROUTER: - if (sock->user_data) { - session_key = ((SilcServerList *)sock->user_data)->receive_key; - hmac = ((SilcServerList *)sock->user_data)->hmac; - } - break; - default: - if (sock->user_data) { - session_key = ((SilcIDListUnknown *)sock->user_data)->receive_key; - hmac = ((SilcIDListUnknown *)sock->user_data)->hmac; - } - } - - /* Decrypt rest of the header plus padding */ - if (session_key) { - unsigned short truelen, len1, len2, padlen; - - /* Pull MAC from packet before decryption */ - if (hmac) { - mac_len = hmac->hash->hash->hash_len; - if ((buffer->len - mac_len) > SILC_PACKET_MIN_LEN) { - silc_buffer_push_tail(buffer, mac_len); - } else { - SILC_LOG_DEBUG(("Bad MAC length in packet, packet dropped")); - return FALSE; - } - } - - SILC_LOG_DEBUG(("Decrypting rest of the header")); - - SILC_GET16_MSB(len1, &buffer->data[4]); - SILC_GET16_MSB(len2, &buffer->data[6]); - - truelen = SILC_PACKET_HEADER_LEN + len1 + len2; - padlen = SILC_PACKET_PADLEN(truelen); - len1 = (truelen + padlen) - (SILC_PACKET_MIN_HEADER_LEN - 2); - - silc_buffer_pull(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - silc_packet_decrypt(session_key, buffer, len1); - silc_buffer_push(buffer, SILC_PACKET_MIN_HEADER_LEN - 2); - } - - return TRUE; -} - -/* Parses whole packet, received earlier. This packet is usually received - from client. */ - -SILC_TASK_CALLBACK(silc_server_packet_parse) -{ - SilcServerInternalPacket *packet = (SilcServerInternalPacket *)context; - SilcServer server = packet->server; - SilcSocketConnection sock = packet->sock; - SilcBuffer buffer = packet->packetdata->buffer; - int ret; - - SILC_LOG_DEBUG(("Start")); - - /* Decrypt start of the packet header */ - if (packet->cipher) - silc_packet_decrypt(packet->cipher, buffer, SILC_PACKET_MIN_HEADER_LEN); - - /* If the packet type is not any special type lets decrypt rest - of the packet here. */ - if (buffer->data[3] != SILC_PACKET_CHANNEL_MESSAGE && - buffer->data[3] != SILC_PACKET_PRIVATE_MESSAGE) { - normal: - /* Normal packet, decrypt rest of the packet */ - if (!silc_server_packet_decrypt_rest(server, sock, buffer)) - goto out; - - /* Parse the packet. Packet type is returned. */ - ret = silc_packet_parse(packet->packetdata); - if (ret == SILC_PACKET_NONE) - goto out; - - /* Check MAC */ - if (!silc_server_packet_check_mac(server, sock, buffer)) - goto out; - } else { - /* If private message key is not set for private message it is - handled as normal packet. Go back up. */ - if (buffer->data[3] == SILC_PACKET_PRIVATE_MESSAGE && - !(buffer->data[2] & SILC_PACKET_FLAG_PRIVMSG_KEY)) - goto normal; - - /* Packet requires special handling, decrypt rest of the header. - This only decrypts. This does not do any MAC checking, it must - be done individually later when doing the special processing. */ - silc_server_packet_decrypt_rest_special(server, sock, buffer); - - /* Parse the packet header in special way as this is "special" - packet type. */ - ret = silc_packet_parse_special(packet->packetdata); - if (ret == SILC_PACKET_NONE) - goto out; - } - - /* Parse the incoming packet type */ - silc_server_packet_parse_type(server, sock, packet->packetdata); - - out: - silc_buffer_clear(sock->inbuf); - // silc_buffer_free(packetdata->packetdata->buffer); - silc_free(packet->packetdata); - silc_free(packet); -} - -/* Parses the packet type and calls what ever routines the packet type - requires. This is done for all incoming packets. */ +/* Parses the packet type and calls what ever routines the packet type + requires. This is done for all incoming packets. */ void silc_server_packet_parse_type(SilcServer server, SilcSocketConnection sock, SilcPacketContext *packet) { - SilcBuffer buffer = packet->buffer; SilcPacketType type = packet->type; + SilcIDListData idata = (SilcIDListData)sock->user_data; SILC_LOG_DEBUG(("Parsing packet type %d", type)); /* Parse the packet type */ - switch(type) { + switch (type) { case SILC_PACKET_DISCONNECT: SILC_LOG_DEBUG(("Disconnect packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; break; + case SILC_PACKET_SUCCESS: /* * Success received for something. For now we can have only @@ -1587,29 +1737,66 @@ void silc_server_packet_parse_type(SilcServer server, * success message is for whatever protocol is executing currently. */ SILC_LOG_DEBUG(("Success packet")); - if (sock->protocol) { - sock->protocol->execute(server->timeout_queue, 0, - sock->protocol, sock->sock, 0, 0); - } + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + if (sock->protocol) + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); break; + case SILC_PACKET_FAILURE: + /* + * Failure received for something. For now we can have only + * one protocol for connection executing at once hence this + * failure message is for whatever protocol is executing currently. + */ SILC_LOG_DEBUG(("Failure packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + if (sock->protocol) { + SilcServerFailureContext f; + f = silc_calloc(1, sizeof(*f)); + f->server = server; + f->sock = sock; + + /* We will wait 5 seconds to process this failure packet */ + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_failure_callback, (void *)f, 5, 0, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); + } break; + case SILC_PACKET_REJECT: SILC_LOG_DEBUG(("Reject packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; return; break; + case SILC_PACKET_NOTIFY: + /* + * Received notify packet. Server can receive notify packets from + * router. Server then relays the notify messages to clients if needed. + */ + SILC_LOG_DEBUG(("Notify packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + silc_server_notify_list(server, sock, packet); + else + silc_server_notify(server, sock, packet); + break; + /* * Channel packets */ case SILC_PACKET_CHANNEL_MESSAGE: /* * Received channel message. Channel messages are special packets - * (although probably most common ones) hence they are handled + * (although probably most common ones) thus they are handled * specially. */ SILC_LOG_DEBUG(("Channel Message packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + idata->last_receive = time(NULL); silc_server_channel_message(server, sock, packet); break; @@ -1621,6 +1808,8 @@ void silc_server_packet_parse_type(SilcServer server, * never receives this channel and thus is ignored. */ SILC_LOG_DEBUG(("Channel Key packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; silc_server_channel_key(server, sock, packet); break; @@ -1628,48 +1817,26 @@ void silc_server_packet_parse_type(SilcServer server, * Command packets */ case SILC_PACKET_COMMAND: - { - /* - * Recived command. Allocate command context and execute the command. - */ - SilcServerCommandContext ctx; - - SILC_LOG_DEBUG(("Command packet")); - - /* Router cannot send command packet */ - if (sock->type == SILC_SOCKET_TYPE_ROUTER) - break; - - /* Allocate command context. This must be free'd by the - command routine receiving it. */ - ctx = silc_calloc(1, sizeof(*ctx)); - ctx->server = server; - ctx->sock = sock; - ctx->packet = packet; /* Save original packet */ - - /* Parse the command payload in the packet */ - ctx->payload = silc_command_parse_payload(buffer); - if (!ctx->payload) { - SILC_LOG_ERROR(("Bad command payload, packet dropped")); - silc_free(ctx); - return; - } - - /* Execute command. If this fails the packet is dropped. */ - SILC_SERVER_COMMAND_EXEC(ctx); - silc_buffer_free(buffer); - } + /* + * Recived command. Processes the command request and allocates the + * command context and calls the command. + */ + SILC_LOG_DEBUG(("Command packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_command_process(server, sock, packet); break; case SILC_PACKET_COMMAND_REPLY: /* - * Received command reply packet. Servers never send commands thus - * they don't receive command reply packets either, except in cases - * where server has forwarded command packet coming from client. - * This must be the case here or we will ignore the packet. + * Received command reply packet. Received command reply to command. It + * may be reply to command sent by us or reply to command sent by client + * that we've routed further. */ SILC_LOG_DEBUG(("Command Reply packet")); - silc_server_packet_relay_command_reply(server, sock, packet); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_command_reply(server, sock, packet); break; /* @@ -1681,10 +1848,19 @@ void silc_server_packet_parse_type(SilcServer server, * client or server. */ SILC_LOG_DEBUG(("Private Message packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + idata->last_receive = time(NULL); silc_server_private_message(server, sock, packet); break; case SILC_PACKET_PRIVATE_MESSAGE_KEY: + /* + * Private message key packet. + */ + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_private_message_key(server, sock, packet); break; /* @@ -1692,44 +1868,63 @@ void silc_server_packet_parse_type(SilcServer server, */ case SILC_PACKET_KEY_EXCHANGE: SILC_LOG_DEBUG(("KE packet")); - if (sock->protocol && sock->protocol->protocol->type - == SILC_PROTOCOL_SERVER_KEY_EXCHANGE) { + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + + if (sock->protocol && sock->protocol->protocol && + sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_KEY_EXCHANGE) { SilcServerKEInternalContext *proto_ctx = (SilcServerKEInternalContext *)sock->protocol->context; - proto_ctx->packet = buffer; + proto_ctx->packet = silc_packet_context_dup(packet); /* Let the protocol handle the packet */ - sock->protocol->execute(server->timeout_queue, 0, - sock->protocol, sock->sock, 0, 100000); + silc_protocol_execute(sock->protocol, server->schedule, 0, 100000); } else { SILC_LOG_ERROR(("Received Key Exchange packet but no key exchange " "protocol active, packet dropped.")); - - /* XXX Trigger KE protocol?? Rekey actually, maybe. */ } break; case SILC_PACKET_KEY_EXCHANGE_1: SILC_LOG_DEBUG(("KE 1 packet")); - if (sock->protocol && sock->protocol->protocol->type - == SILC_PROTOCOL_SERVER_KEY_EXCHANGE) { + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; - SilcServerKEInternalContext *proto_ctx = - (SilcServerKEInternalContext *)sock->protocol->context; + if (sock->protocol && sock->protocol->protocol && + (sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_KEY_EXCHANGE || + sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_REKEY)) { - if (proto_ctx->packet) - silc_buffer_free(proto_ctx->packet); + if (sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_REKEY) { + SilcServerRekeyInternalContext *proto_ctx = + (SilcServerRekeyInternalContext *)sock->protocol->context; + + if (proto_ctx->packet) + silc_packet_context_free(proto_ctx->packet); + + proto_ctx->packet = silc_packet_context_dup(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 */ + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); + } else { + SilcServerKEInternalContext *proto_ctx = + (SilcServerKEInternalContext *)sock->protocol->context; + + if (proto_ctx->packet) + silc_packet_context_free(proto_ctx->packet); + + proto_ctx->packet = silc_packet_context_dup(packet); + proto_ctx->dest_id_type = packet->src_id_type; + proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len, + packet->src_id_type); + if (!proto_ctx->dest_id) + break; - /* Let the protocol handle the packet */ - sock->protocol->execute(server->timeout_queue, 0, - sock->protocol, sock->sock, + /* Let the protocol handle the packet */ + silc_protocol_execute(sock->protocol, server->schedule, 0, 100000); + } } else { SILC_LOG_ERROR(("Received Key Exchange 1 packet but no key exchange " "protocol active, packet dropped.")); @@ -1738,23 +1933,42 @@ void silc_server_packet_parse_type(SilcServer server, case SILC_PACKET_KEY_EXCHANGE_2: SILC_LOG_DEBUG(("KE 2 packet")); - if (sock->protocol && sock->protocol->protocol->type - == SILC_PROTOCOL_SERVER_KEY_EXCHANGE) { + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; - SilcServerKEInternalContext *proto_ctx = - (SilcServerKEInternalContext *)sock->protocol->context; + if (sock->protocol && sock->protocol->protocol && + (sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_KEY_EXCHANGE || + sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_REKEY)) { - if (proto_ctx->packet) - silc_buffer_free(proto_ctx->packet); + if (sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_REKEY) { + SilcServerRekeyInternalContext *proto_ctx = + (SilcServerRekeyInternalContext *)sock->protocol->context; + + if (proto_ctx->packet) + silc_packet_context_free(proto_ctx->packet); + + proto_ctx->packet = silc_packet_context_dup(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 */ + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); + } else { + SilcServerKEInternalContext *proto_ctx = + (SilcServerKEInternalContext *)sock->protocol->context; + + if (proto_ctx->packet) + silc_packet_context_free(proto_ctx->packet); + + proto_ctx->packet = silc_packet_context_dup(packet); + proto_ctx->dest_id_type = packet->src_id_type; + proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len, + packet->src_id_type); + if (!proto_ctx->dest_id) + break; - /* Let the protocol handle the packet */ - sock->protocol->execute(server->timeout_queue, 0, - sock->protocol, sock->sock, + /* Let the protocol handle the packet */ + silc_protocol_execute(sock->protocol, server->schedule, 0, 100000); + } } else { SILC_LOG_ERROR(("Received Key Exchange 2 packet but no key exchange " "protocol active, packet dropped.")); @@ -1762,9 +1976,17 @@ void silc_server_packet_parse_type(SilcServer server, break; case SILC_PACKET_CONNECTION_AUTH_REQUEST: - /* If we receive this packet we will send to the other end information - about our mandatory authentication method for the connection. - This packet maybe received at any time. */ + /* + * Connection authentication request packet. When we receive this packet + * we will send to the other end information about our mandatory + * authentication method for the connection. This packet maybe received + * at any time. + */ + SILC_LOG_DEBUG(("Connection authentication request packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_connection_auth_request(server, sock, packet); + break; /* * Connection Authentication protocol packets @@ -1773,17 +1995,19 @@ void silc_server_packet_parse_type(SilcServer server, /* Start of the authentication protocol. We receive here the authentication data and will verify it. */ SILC_LOG_DEBUG(("Connection auth packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + if (sock->protocol && sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_CONNECTION_AUTH) { SilcServerConnAuthInternalContext *proto_ctx = (SilcServerConnAuthInternalContext *)sock->protocol->context; - proto_ctx->packet = buffer; + proto_ctx->packet = silc_packet_context_dup(packet); /* Let the protocol handle the packet */ - sock->protocol->execute(server->timeout_queue, 0, - sock->protocol, sock->sock, 0, 0); + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); } else { SILC_LOG_ERROR(("Received Connection Auth packet but no authentication " "protocol active, packet dropped.")); @@ -1798,7 +2022,10 @@ void silc_server_packet_parse_type(SilcServer server, * SILC network. */ SILC_LOG_DEBUG(("New ID packet")); - silc_server_new_id(server, sock, packet); + if (packet->flags & SILC_PACKET_FLAG_LIST) + silc_server_new_id_list(server, sock, packet); + else + silc_server_new_id(server, sock, packet); break; case SILC_PACKET_NEW_CLIENT: @@ -1808,1919 +2035,1836 @@ void silc_server_packet_parse_type(SilcServer server, * ID we will send it to the client. */ SILC_LOG_DEBUG(("New Client packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; silc_server_new_client(server, sock, packet); break; case SILC_PACKET_NEW_SERVER: /* * Received new server packet. This includes Server ID and some other - * information that we may save. This is after server as connected to us. + * information that we may save. This is received after server has + * connected to us. */ SILC_LOG_DEBUG(("New Server packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; silc_server_new_server(server, sock, packet); break; - default: - SILC_LOG_ERROR(("Incorrect packet type %d, packet dropped", type)); + case SILC_PACKET_NEW_CHANNEL: + /* + * Received new channel packet. Information about new channel in the + * network are distributed using this packet. + */ + SILC_LOG_DEBUG(("New Channel packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + silc_server_new_channel_list(server, sock, packet); + else + silc_server_new_channel(server, sock, packet); break; - } - -} - -/* 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 error. */ - -static int silc_server_packet_send_real(SilcServer server, - SilcSocketConnection sock, - int force_send) -{ - /* Send now if forced to do so */ - if (force_send == TRUE) { - int ret; - SILC_LOG_DEBUG(("Forcing packet send, packet sent immediately")); - ret = silc_packet_write(sock->sock, sock->outbuf); - - silc_buffer_clear(sock->outbuf); - - if (ret == -1) - SILC_LOG_ERROR(("Could not write, packet dropped")); - if (ret != -2) { - silc_buffer_clear(sock->outbuf); - return ret; - } - SILC_LOG_DEBUG(("Could not force the send, packet put to queue")); - } - - SILC_LOG_DEBUG(("Packet in queue")); + case SILC_PACKET_HEARTBEAT: + /* + * Received heartbeat. + */ + SILC_LOG_DEBUG(("Heartbeat packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + break; - /* 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_server_packet_process. */ - SILC_SET_CONNECTION_FOR_OUTPUT(sock->sock); + case SILC_PACKET_KEY_AGREEMENT: + /* + * Received heartbeat. + */ + SILC_LOG_DEBUG(("Key agreement packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_key_agreement(server, sock, packet); + break; - /* 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); + case SILC_PACKET_REKEY: + /* + * Received re-key packet. The sender wants to regenerate the session + * keys. + */ + SILC_LOG_DEBUG(("Re-key packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_rekey(server, sock, packet); + break; - return 0; -} + case SILC_PACKET_REKEY_DONE: + /* + * The re-key is done. + */ + SILC_LOG_DEBUG(("Re-key done packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; -/* Prepare outgoing data buffer for packet sending. This is internal - routine and must always be called before sending any packets out. */ + if (sock->protocol && sock->protocol->protocol && + sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_REKEY) { -static void silc_server_packet_send_prepare(SilcServer server, - SilcSocketConnection sock, - unsigned int header_len, - unsigned int padlen, - unsigned int data_len) -{ - int totlen, oldlen; + SilcServerRekeyInternalContext *proto_ctx = + (SilcServerRekeyInternalContext *)sock->protocol->context; - totlen = header_len + padlen + data_len; + if (proto_ctx->packet) + silc_packet_context_free(proto_ctx->packet); - /* Prepare the outgoing buffer for packet sending. */ - if (!sock->outbuf) { - /* Allocate new buffer. This is done only once per connection. */ - SILC_LOG_DEBUG(("Allocating outgoing data buffer")); - - sock->outbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE); - silc_buffer_pull_tail(sock->outbuf, totlen); - silc_buffer_pull(sock->outbuf, header_len + padlen); - } else { - if (SILC_IS_OUTBUF_PENDING(sock)) { - /* There is some pending data in the buffer. */ + proto_ctx->packet = silc_packet_context_dup(packet); - if ((sock->outbuf->end - sock->outbuf->tail) < data_len) { - SILC_LOG_DEBUG(("Reallocating outgoing data buffer")); - /* XXX: not done yet */ - } - oldlen = sock->outbuf->len; - silc_buffer_pull_tail(sock->outbuf, totlen); - silc_buffer_pull(sock->outbuf, header_len + padlen + oldlen); + /* Let the protocol handle the packet */ + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); } else { - /* Buffer is free for use */ - silc_buffer_clear(sock->outbuf); - silc_buffer_pull_tail(sock->outbuf, totlen); - silc_buffer_pull(sock->outbuf, header_len + padlen); + SILC_LOG_ERROR(("Received Re-key done packet but no re-key " + "protocol active, packet dropped.")); } - } -} - -/* Assembles a new packet to be sent out to network. This doesn't actually - send the packet but creates the packet and fills the outgoing data - buffer and marks the packet ready to be sent to network. However, If - argument force_send is TRUE the packet is sent immediately and not put - to queue. Normal case is that the packet is not sent immediately. */ - -void silc_server_packet_send(SilcServer server, - SilcSocketConnection sock, - SilcPacketType type, - SilcPacketFlags flags, - unsigned char *data, - unsigned int data_len, - int force_send) -{ - void *dst_id = NULL; - SilcIdType dst_id_type = SILC_ID_NONE; + break; - /* Get data used in the packet sending, keys and stuff */ - switch(sock->type) { - case SILC_SOCKET_TYPE_CLIENT: - if (((SilcClientList *)sock->user_data)->id) { - dst_id = ((SilcClientList *)sock->user_data)->id; - dst_id_type = SILC_ID_CLIENT; - } + case SILC_PACKET_FTP: + /* FTP packet */ + SILC_LOG_DEBUG(("FTP packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_ftp(server, sock, packet); break; - case SILC_SOCKET_TYPE_SERVER: - case SILC_SOCKET_TYPE_ROUTER: - if (((SilcServerList *)sock->user_data)->id) { - dst_id = ((SilcServerList *)sock->user_data)->id; - dst_id_type = SILC_ID_SERVER; - } + + case SILC_PACKET_RESUME_ROUTER: + /* Resume router packet received. This packet is received for backup + router resuming protocol. */ + SILC_LOG_DEBUG(("Resume router packet")); + if (packet->flags & SILC_PACKET_FLAG_LIST) + break; + silc_server_backup_resume_router(server, sock, packet); break; + default: + SILC_LOG_ERROR(("Incorrect packet type %d, packet dropped", type)); break; } - - silc_server_packet_send_dest(server, sock, type, flags, dst_id, - dst_id_type, data, data_len, force_send); + } -/* Assembles a new packet to be sent out to network. This doesn't actually - send the packet but creates the packet and fills the outgoing data - buffer and marks the packet ready to be sent to network. However, If - argument force_send is TRUE the packet is sent immediately and not put - to queue. Normal case is that the packet is not sent immediately. - Destination information is sent as argument for this function. */ - -void silc_server_packet_send_dest(SilcServer server, - SilcSocketConnection sock, - SilcPacketType type, - SilcPacketFlags flags, - void *dst_id, - SilcIdType dst_id_type, - unsigned char *data, - unsigned int data_len, - int force_send) +/* Creates connection to a remote router. */ + +void silc_server_create_connection(SilcServer server, + char *remote_host, uint32 port) { - SilcPacketContext packetdata; - SilcCipher cipher = NULL; - SilcHmac hmac = NULL; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned char mac[32]; - unsigned int mac_len = 0; - unsigned char *dst_id_data = NULL; - unsigned int dst_id_len = 0; + SilcServerConnection sconn; - SILC_LOG_DEBUG(("Sending packet, type %d", type)); + /* Allocate connection object for hold connection specific stuff. */ + sconn = silc_calloc(1, sizeof(*sconn)); + sconn->server = server; + sconn->remote_host = strdup(remote_host); + sconn->remote_port = port; - /* Get data used in the packet sending, keys and stuff */ - switch(sock->type) { - case SILC_SOCKET_TYPE_CLIENT: - if (sock->user_data) { - cipher = ((SilcClientList *)sock->user_data)->send_key; - hmac = ((SilcClientList *)sock->user_data)->hmac; - if (hmac) { - mac_len = hmac->hash->hash->hash_len; - hmac_key = ((SilcClientList *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcClientList *)sock->user_data)->hmac_key_len; - } - } - break; - case SILC_SOCKET_TYPE_SERVER: - case SILC_SOCKET_TYPE_ROUTER: - if (sock->user_data) { - cipher = ((SilcServerList *)sock->user_data)->send_key; - hmac = ((SilcServerList *)sock->user_data)->hmac; - if (hmac) { - mac_len = hmac->hash->hash->hash_len; - hmac_key = ((SilcServerList *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcServerList *)sock->user_data)->hmac_key_len; - } - } - break; - default: - if (sock->user_data) { - /* We don't know what type of connection this is thus it must - be in authentication phase. */ - cipher = ((SilcIDListUnknown *)sock->user_data)->send_key; - hmac = ((SilcIDListUnknown *)sock->user_data)->hmac; - if (hmac) { - mac_len = hmac->hash->hash->hash_len; - hmac_key = ((SilcIDListUnknown *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcIDListUnknown *)sock->user_data)->hmac_key_len; - } - } - break; - } + silc_schedule_task_add(server->schedule, 0, + silc_server_connect_router, + (void *)sconn, 0, 1, SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); +} - if (dst_id) { - dst_id_data = silc_id_id2str(dst_id, dst_id_type); - dst_id_len = silc_id_get_len(dst_id_type); - } +SILC_TASK_CALLBACK(silc_server_close_connection_final) +{ + silc_socket_free((SilcSocketConnection)context); +} - /* Set the packet context pointers */ - packetdata.type = type; - packetdata.flags = flags; - packetdata.src_id = silc_id_id2str(server->id, server->id_type); - packetdata.src_id_len = SILC_ID_SERVER_LEN; - packetdata.src_id_type = server->id_type; - packetdata.dst_id = dst_id_data; - packetdata.dst_id_len = dst_id_len; - packetdata.dst_id_type = dst_id_type; - packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + dst_id_len; - packetdata.padlen = SILC_PACKET_PADLEN(packetdata.truelen); - packetdata.rng = server->rng; +/* Closes connection to socket connection */ - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, - SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len, - packetdata.padlen, - data_len); +void silc_server_close_connection(SilcServer server, + SilcSocketConnection sock) +{ + if (!server->sockets[sock->sock]) + return; - SILC_LOG_DEBUG(("Putting data to outgoing buffer, len %d", data_len)); + SILC_LOG_INFO(("Closing connection %s:%d [%s]", sock->hostname, + sock->port, + (sock->type == SILC_SOCKET_TYPE_UNKNOWN ? "Unknown" : + sock->type == SILC_SOCKET_TYPE_CLIENT ? "Client" : + sock->type == SILC_SOCKET_TYPE_SERVER ? "Server" : + "Router"))); - packetdata.buffer = sock->outbuf; + /* We won't listen for this connection anymore */ + silc_schedule_unset_listen_fd(server->schedule, sock->sock); - /* Put the data to the buffer */ - if (data && data_len) - silc_buffer_put(sock->outbuf, data, data_len); + /* Unregister all tasks */ + silc_schedule_task_del_by_fd(server->schedule, sock->sock); - /* Create the outgoing packet */ - silc_packet_assemble(&packetdata); + /* Close the actual connection */ + silc_net_close_connection(sock->sock); + server->sockets[sock->sock] = NULL; - /* Compute MAC of the packet */ - if (hmac) { - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); + /* If sock->user_data is NULL then we'll check for active protocols + here since the silc_server_free_sock_user_data has not been called + for this connection. */ + if (!sock->user_data) { + /* If any protocol is active cancel its execution. It will call + the final callback which will finalize the disconnection. */ + if (sock->protocol) { + silc_protocol_cancel(sock->protocol, server->schedule); + sock->protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute_final(sock->protocol, server->schedule); + sock->protocol = NULL; + return; + } } - /* Encrypt the packet */ - if (cipher) - silc_packet_encrypt(cipher, sock->outbuf, sock->outbuf->len); + silc_schedule_task_add(server->schedule, 0, + silc_server_close_connection_final, + (void *)sock, 0, 1, SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); +} - /* Pull MAC into the visible data area */ - if (hmac) - silc_buffer_pull_tail(sock->outbuf, mac_len); - - SILC_LOG_HEXDUMP(("Outgoing packet, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); - - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); - - if (packetdata.src_id) - silc_free(packetdata.src_id); - if (packetdata.dst_id) - silc_free(packetdata.dst_id); -} +/* Sends disconnect message to remote connection and disconnects the + connection. */ -/* Forwards packet. Packets sent with this function will be marked as - forwarded (in the SILC header flags) so that the receiver knows that - we have forwarded the packet to it. Forwarded packets are handled - specially by the receiver as they are not destined to the receiver - originally. However, the receiver knows this because the forwarded - flag has been set (and the flag is authenticated). */ - -void silc_server_packet_forward(SilcServer server, - SilcSocketConnection sock, - unsigned char *data, unsigned int data_len, - int force_send) +void silc_server_disconnect_remote(SilcServer server, + SilcSocketConnection sock, + const char *fmt, ...) { - SilcCipher cipher = NULL; - SilcHmac hmac = NULL; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned char mac[32]; - unsigned int mac_len = 0; - - SILC_LOG_DEBUG(("Forwarding packet")); + va_list ap; + unsigned char buf[4096]; - /* Get data used in the packet sending, keys and stuff */ - switch(sock->type) { - case SILC_SOCKET_TYPE_CLIENT: - if (sock->user_data) { - cipher = ((SilcClientList *)sock->user_data)->send_key; - hmac = ((SilcClientList *)sock->user_data)->hmac; - if (hmac) { - mac_len = hmac->hash->hash->hash_len; - hmac_key = ((SilcClientList *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcClientList *)sock->user_data)->hmac_key_len; - } - } - break; - case SILC_SOCKET_TYPE_SERVER: - case SILC_SOCKET_TYPE_ROUTER: - if (sock->user_data) { - cipher = ((SilcServerList *)sock->user_data)->send_key; - hmac = ((SilcServerList *)sock->user_data)->hmac; - if (hmac) { - mac_len = hmac->hash->hash->hash_len; - hmac_key = ((SilcServerList *)sock->user_data)->hmac_key; - hmac_key_len = ((SilcServerList *)sock->user_data)->hmac_key_len; - } - } - break; - default: - /* We won't forward to unknown destination - keys must exist with - the destination before forwarding. */ + if (!sock) return; - } - - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, 0, 0, data_len); - /* Mungle the packet flags and add the FORWARDED flag */ - if (data) - data[2] |= (unsigned char)SILC_PACKET_FLAG_FORWARDED; + memset(buf, 0, sizeof(buf)); + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); - /* Put the data to the buffer */ - if (data && data_len) - silc_buffer_put(sock->outbuf, data, data_len); + SILC_LOG_DEBUG(("Disconnecting remote host")); - /* Compute MAC of the packet */ - if (hmac) { - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - } + /* Notify remote end that the conversation is over. The notify message + is tried to be sent immediately. */ + silc_server_packet_send(server, sock, SILC_PACKET_DISCONNECT, 0, + buf, strlen(buf), TRUE); - /* Encrypt the packet */ - if (cipher) - silc_packet_encrypt(cipher, sock->outbuf, sock->outbuf->len); + /* Mark the connection to be disconnected */ + SILC_SET_DISCONNECTED(sock); + silc_server_close_connection(server, sock); +} - /* Pull MAC into the visible data area */ - if (hmac) - silc_buffer_pull_tail(sock->outbuf, mac_len); +typedef struct { + SilcServer server; + SilcClientEntry client; +} *FreeClientInternal; - SILC_LOG_HEXDUMP(("Forwarded packet, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); +SILC_TASK_CALLBACK(silc_server_free_client_data_timeout) +{ + FreeClientInternal i = (FreeClientInternal)context; - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); + silc_idlist_del_data(i->client); + silc_idcache_purge_by_context(i->server->local_list->clients, i->client); + silc_free(i); } -/* This routine is used by the server to send packets to channel. The - packet sent with this function is distributed to all clients on - the channel. Usually this is used to send notify messages to the - channel, things like notify about new user joining to the channel. */ +/* Frees client data and notifies about client's signoff. */ -void silc_server_packet_send_to_channel(SilcServer server, - SilcChannelList *channel, - unsigned char *data, - unsigned int data_len, - int force_send) +void silc_server_free_client_data(SilcServer server, + SilcSocketConnection sock, + SilcClientEntry client, + int notify, + char *signoff) { - int i; - SilcSocketConnection sock = NULL; - SilcPacketContext packetdata; - SilcClientList *client = NULL; - SilcServerList **routed = NULL; - unsigned int routed_count = 0; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned char mac[32]; - unsigned int mac_len = 0; - SilcCipher cipher; - SilcHmac hmac; - SilcBuffer payload; - - SILC_LOG_DEBUG(("Sending packet to channel")); - - /* Generate IV */ - for (i = 0; i < 16; i++) - channel->iv[i] = silc_rng_get_byte(server->rng); - - /* Encode the channel payload */ - payload = silc_channel_encode_payload(0, "", data_len, data, - 16, channel->iv, server->rng); - if (!payload) - return; - - /* 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); - - /* Set the packet context pointers. */ - packetdata.flags = 0; - packetdata.type = SILC_PACKET_CHANNEL_MESSAGE; - packetdata.src_id = silc_id_id2str(server->id, SILC_ID_SERVER); - packetdata.src_id_len = SILC_ID_SERVER_LEN; - packetdata.src_id_type = SILC_ID_SERVER; - packetdata.dst_id = silc_id_id2str(channel->id, SILC_ID_CHANNEL); - packetdata.dst_id_len = SILC_ID_CHANNEL_LEN; - packetdata.dst_id_type = SILC_ID_CHANNEL; - packetdata.rng = server->rng; - packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len)); - - /* If there are global users in the channel we will send the message - first to our router for further routing. */ - if (server->server_type == SILC_SERVER && !server->standalone && - channel->global_users) { - SilcServerList *router; - - /* Get data used in packet header encryption, keys and stuff. */ - router = server->id_entry->router; - sock = (SilcSocketConnection)router->connection; - cipher = router->send_key; - hmac = router->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = router->hmac_key; - hmac_key_len = router->hmac_key_len; - - SILC_LOG_DEBUG(("Sending packet to router for routing")); - - packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len; - - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, - SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len, - packetdata.padlen, - payload->len); - packetdata.buffer = sock->outbuf; - - /* Put the original packet into the buffer. */ - silc_buffer_put(sock->outbuf, payload->data, payload->len); - - /* Create the outgoing packet */ - silc_packet_assemble(&packetdata); - - /* Compute MAC of the packet. MAC is computed from the header, - padding and the relayed packet. */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - /* Encrypt the header and padding of the packet. This is encrypted - with normal session key shared with the client. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); - - SILC_LOG_HEXDUMP(("Channel packet, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); - - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); - } - - /* Send the message to clients on the channel's client list. */ - for (i = 0; i < channel->user_list_count; i++) { - client = channel->user_list[i].client; + FreeClientInternal i = silc_calloc(1, sizeof(*i)); - /* If client has router set it is not locally connected client and - we will route the message to the router set in the client. */ - if (client && client->router && server->server_type == SILC_ROUTER) { - int k; + /* If there is pending outgoing data for the client then purge it + to the network before removing the client entry. */ + silc_server_packet_queue_purge(server, sock); - /* Check if we have sent the packet to this route already */ - for (k = 0; k < routed_count; k++) - if (routed[k] == client->router) - continue; + if (!client->id) + return; - /* Get data used in packet header encryption, keys and stuff. */ - sock = (SilcSocketConnection)client->router->connection; - cipher = client->router->send_key; - hmac = client->router->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = client->router->hmac_key; - hmac_key_len = client->router->hmac_key_len; - - packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len; - - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, - SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len, - packetdata.padlen, - payload->len); - packetdata.buffer = sock->outbuf; - - /* Put the encrypted payload data into the buffer. */ - silc_buffer_put(sock->outbuf, payload->data, payload->len); - - /* Create the outgoing packet */ - silc_packet_assemble(&packetdata); - - /* Compute MAC of the packet. MAC is computed from the header, - padding and the relayed packet. */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - /* Encrypt the header and padding of the packet. This is encrypted - with normal session key shared with the client. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); - - SILC_LOG_HEXDUMP(("Packet to channel, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); + /* Send SIGNOFF notify to routers. */ + if (notify && !server->standalone && server->router) + silc_server_send_notify_signoff(server, server->router->connection, + server->server_type == SILC_SERVER ? + FALSE : TRUE, client->id, signoff); + + /* Remove client from all channels */ + if (notify) + silc_server_remove_from_channels(server, NULL, client, + TRUE, signoff, TRUE); + else + silc_server_remove_from_channels(server, NULL, client, + FALSE, NULL, FALSE); + + /* We will not delete the client entry right away. We will take it + into history (for WHOWAS command) for 5 minutes */ + i->server = server; + i->client = client; + silc_schedule_task_add(server->schedule, 0, + silc_server_free_client_data_timeout, + (void *)i, 300, 0, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW); + client->data.status &= ~SILC_IDLIST_STATUS_REGISTERED; + client->router = NULL; + client->connection = NULL; + + /* Free the client entry and everything in it */ + server->stat.my_clients--; + server->stat.clients--; + if (server->server_type == SILC_ROUTER) + server->stat.cell_clients--; +} - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); +/* Frees user_data pointer from socket connection object. This also sends + appropriate notify packets to the network to inform about leaving + entities. */ - /* We want to make sure that the packet is routed to same router - only once. Mark this route as sent route. */ - k = routed_count; - routed = silc_realloc(routed, sizeof(*routed) * (k + 1)); - routed[k] = client->router; - routed_count++; +void silc_server_free_sock_user_data(SilcServer server, + SilcSocketConnection sock) +{ + SILC_LOG_DEBUG(("Start")); - continue; + switch (sock->type) { + case SILC_SOCKET_TYPE_CLIENT: + { + SilcClientEntry user_data = (SilcClientEntry)sock->user_data; + silc_server_free_client_data(server, sock, user_data, TRUE, NULL); + break; } + case SILC_SOCKET_TYPE_SERVER: + case SILC_SOCKET_TYPE_ROUTER: + { + SilcServerEntry user_data = (SilcServerEntry)sock->user_data; + SilcServerEntry backup_router = NULL; + + if (user_data->id) + backup_router = silc_server_backup_get(server, user_data->id); + + /* If this was our primary router connection then we're lost to + the outside world. */ + if (server->router == user_data) { + /* Check whether we have a backup router connection */ + if (!backup_router || backup_router == user_data) { + silc_schedule_task_add(server->schedule, 0, + silc_server_connect_to_router, + server, 1, 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); + + server->id_entry->router = NULL; + server->router = NULL; + server->standalone = TRUE; + backup_router = NULL; + } else { + SILC_LOG_INFO(("New primary router is backup router %s", + backup_router->server_name)); + SILC_LOG_DEBUG(("New primary router is backup router %s", + backup_router->server_name)); + server->id_entry->router = backup_router; + server->router = backup_router; + server->router_connect = time(0); + server->backup_primary = TRUE; + if (server->server_type == SILC_BACKUP_ROUTER) { + server->server_type = SILC_ROUTER; + + /* We'll need to constantly try to reconnect to the primary + router so that we'll see when it comes back online. */ + silc_server_backup_reconnect(server, sock->ip, sock->port, + silc_server_backup_connected, + NULL); + } - /* Send to locally connected client */ - if (client) { + /* Mark this connection as replaced */ + silc_server_backup_replaced_add(server, user_data->id, + backup_router); + } + } else if (backup_router) { + SILC_LOG_INFO(("Enabling the use of backup router %s", + backup_router->server_name)); + SILC_LOG_DEBUG(("Enabling the use of backup router %s", + backup_router->server_name)); + + /* Mark this connection as replaced */ + silc_server_backup_replaced_add(server, user_data->id, + backup_router); + } - /* XXX Check client's mode on the channel. */ + if (!backup_router) { + /* Free all client entries that this server owns as they will + become invalid now as well. */ + if (user_data->id) + silc_server_remove_clients_by_server(server, user_data, TRUE); + } else { + /* Update the client entries of this server to the new backup + router. This also removes the clients that *really* was owned + by the primary router and went down with the router. */ + silc_server_update_clients_by_server(server, user_data, backup_router, + TRUE, TRUE); + silc_server_update_servers_by_server(server, user_data, backup_router); + } - /* Get data used in packet header encryption, keys and stuff. */ - sock = (SilcSocketConnection)client->connection; - cipher = client->send_key; - hmac = client->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = client->hmac_key; - hmac_key_len = client->hmac_key_len; - - packetdata.truelen = payload->len + SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len; - - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, - SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len, - packetdata.padlen, - payload->len); - packetdata.buffer = sock->outbuf; - - /* Put the encrypted payload data into the buffer. */ - silc_buffer_put(sock->outbuf, payload->data, payload->len); - - /* Create the outgoing packet */ - silc_packet_assemble(&packetdata); - - /* Compute MAC of the packet. MAC is computed from the header, - padding and the relayed packet. */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - /* Encrypt the header and padding of the packet. This is encrypted - with normal session key shared with the client. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); - - SILC_LOG_HEXDUMP(("Packet to channel, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); + /* Free the server entry */ + silc_server_backup_del(server, user_data); + silc_server_backup_replaced_del(server, user_data); + silc_idlist_del_data(user_data); + if (!silc_idlist_del_server(server->local_list, user_data)) + silc_idlist_del_server(server->global_list, user_data); + server->stat.my_servers--; + server->stat.servers--; + if (server->server_type == SILC_ROUTER) + server->stat.cell_servers--; + + if (backup_router) { + /* Announce all of our stuff that was created about 5 minutes ago. + The backup router knows all the other stuff already. */ + if (server->server_type == SILC_ROUTER) + silc_server_announce_servers(server, FALSE, time(0) - 300, + backup_router->connection); + + /* Announce our clients and channels to the router */ + silc_server_announce_clients(server, time(0) - 300, + backup_router->connection); + silc_server_announce_channels(server, time(0) - 300, + backup_router->connection); + } + break; + } + default: + { + SilcUnknownEntry user_data = (SilcUnknownEntry)sock->user_data; - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); + silc_idlist_del_data(user_data); + silc_free(user_data); + break; } } - if (routed_count) - silc_free(routed); - silc_free(packetdata.src_id); - silc_free(packetdata.dst_id); - silc_buffer_free(payload); + /* If any protocol is active cancel its execution */ + if (sock->protocol) { + silc_protocol_cancel(sock->protocol, server->schedule); + sock->protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute_final(sock->protocol, server->schedule); + sock->protocol = NULL; + } + + sock->user_data = NULL; } -/* This routine is explicitly used to relay messages to some channel. - Packets sent with this function we have received earlier and are - totally encrypted. This just sends the packet to all clients on - the channel. If the sender of the packet is someone on the channel - the message will not be sent to that client. The SILC Packet header - is encrypted with the session key shared between us and the client. - MAC is also computed before encrypting the header. Rest of the - packet will be untouched. */ - -void silc_server_packet_relay_to_channel(SilcServer server, - SilcSocketConnection sender_sock, - SilcChannelList *channel, - void *sender, - SilcIdType sender_type, - unsigned char *data, - unsigned int data_len, - int force_send) +/* Removes client from all channels it has joined. This is used when client + connection is disconnected. If the client on a channel is last, the + channel is removed as well. This sends the SIGNOFF notify types. */ + +void silc_server_remove_from_channels(SilcServer server, + SilcSocketConnection sock, + SilcClientEntry client, + int notify, + char *signoff_message, + int keygen) { - int i, found = FALSE; - SilcSocketConnection sock = NULL; - SilcPacketContext packetdata; - SilcClientList *client = NULL; - SilcServerList **routed = NULL; - unsigned int routed_count = 0; - unsigned char *hmac_key = NULL; - unsigned int hmac_key_len = 0; - unsigned char mac[32]; - unsigned int mac_len = 0; - SilcCipher cipher; - SilcHmac hmac; - - SILC_LOG_DEBUG(("Relaying packet to channel")); - - SILC_LOG_HEXDUMP(("XXX %d", data_len), data, data_len); - - /* Set the packet context pointers. */ - packetdata.flags = 0; - packetdata.type = SILC_PACKET_CHANNEL_MESSAGE; - packetdata.src_id = silc_id_id2str(sender, sender_type); - packetdata.src_id_len = silc_id_get_len(sender_type); - packetdata.src_id_type = sender_type; - packetdata.dst_id = silc_id_id2str(channel->id, SILC_ID_CHANNEL); - packetdata.dst_id_len = SILC_ID_CHANNEL_LEN; - packetdata.dst_id_type = SILC_ID_CHANNEL; - packetdata.rng = server->rng; - packetdata.padlen = SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len)); - - /* If there are global users in the channel we will send the message - first to our router for further routing. */ - if (server->server_type == SILC_SERVER && !server->standalone && - channel->global_users) { - SilcServerList *router; - - router = server->id_entry->router; - - /* Check that the sender is not our router. */ - if (sender_sock != (SilcSocketConnection)router->connection) { - - /* Get data used in packet header encryption, keys and stuff. */ - sock = (SilcSocketConnection)router->connection; - cipher = router->send_key; - hmac = router->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = router->hmac_key; - hmac_key_len = router->hmac_key_len; - - SILC_LOG_DEBUG(("Sending packet to router for routing")); - - packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len; - - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, - SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len, - packetdata.padlen, - data_len); - packetdata.buffer = sock->outbuf; - - /* Put the original packet into the buffer. */ - silc_buffer_put(sock->outbuf, data, data_len); - - /* Create the outgoing packet */ - silc_packet_assemble(&packetdata); - - /* Compute MAC of the packet. MAC is computed from the header, - padding and the relayed packet. */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - /* Encrypt the header and padding of the packet. This is encrypted - with normal session key shared with the client. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); - - SILC_LOG_HEXDUMP(("Channel packet, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); - - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); - } - } + SilcChannelEntry channel; + SilcChannelClientEntry chl; + SilcHashTableList htl; + SilcBuffer clidp; - /* Send the message to clients on the channel's client list. */ - for (i = 0; i < channel->user_list_count; i++) { - client = channel->user_list[i].client; + SILC_LOG_DEBUG(("Start")); - if (client) { + if (!client || !client->id) + return; - /* If sender is one on the channel do not send it the packet. */ - if (!found && !SILC_ID_CLIENT_COMPARE(client->id, sender)) { - found = TRUE; - continue; - } + clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); - /* If the client has set router it means that it is not locally - connected client and we won't send message to those in this - function (they will be routed separately by the caller). */ - if (server->server_type == SILC_ROUTER && client->router) { - int k; + /* Remove the client from all channels. The client is removed from + the channels' user list. */ + silc_hash_table_list(client->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + channel = chl->channel; + + /* Remove channel from client's channel list */ + silc_hash_table_del(client->channels, channel); + + /* Remove channel if there is no users anymore */ + if (server->server_type == SILC_ROUTER && + silc_hash_table_count(channel->user_list) < 2) { + if (channel->rekey) + silc_schedule_task_del_by_context(server->schedule, channel->rekey); + if (silc_idlist_del_channel(server->local_list, channel)) + server->stat.my_channels--; + else + silc_idlist_del_channel(server->global_list, channel); + continue; + } - /* Sender maybe server as well so we want to make sure that - we won't send the message to the server it came from. */ - if (!found && !SILC_ID_SERVER_COMPARE(client->router->id, sender)) { - found = TRUE; - continue; + /* Remove client from channel's client list */ + silc_hash_table_del(channel->user_list, chl->client); + + /* If there is no global users on the channel anymore mark the channel + as local channel. Do not check if the removed client is local client. */ + if (server->server_type != SILC_ROUTER && channel->global_users && + chl->client->router && !silc_server_channel_has_global(channel)) + channel->global_users = FALSE; + + silc_free(chl); + server->stat.my_chanclients--; + + /* If there is not at least one local user on the channel then we don't + need the channel entry anymore, we can remove it safely. */ + if (server->server_type != SILC_ROUTER && + !silc_server_channel_has_local(channel)) { + /* Notify about leaving client if this channel has global users. */ + if (notify && channel->global_users) + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_SIGNOFF, + signoff_message ? 2 : 1, + clidp->data, clidp->len, + signoff_message, signoff_message ? + strlen(signoff_message) : 0); + + if (channel->rekey) + silc_schedule_task_del_by_context(server->schedule, channel->rekey); + + if (channel->founder_key) { + /* The founder auth data exists, do not remove the channel entry */ + SilcChannelClientEntry chl2; + SilcHashTableList htl2; + + channel->disabled = TRUE; + + silc_hash_table_list(channel->user_list, &htl2); + while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) { + silc_hash_table_del(chl2->client->channels, channel); + silc_hash_table_del(channel->user_list, chl2->client); + silc_free(chl2); } - - /* Check if we have sent the packet to this route already */ - for (k = 0; k < routed_count; k++) - if (routed[k] == client->router) - continue; - - /* Get data used in packet header encryption, keys and stuff. */ - sock = (SilcSocketConnection)client->router->connection; - cipher = client->router->send_key; - hmac = client->router->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = client->router->hmac_key; - hmac_key_len = client->router->hmac_key_len; - - packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len; - - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, - SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len, - packetdata.padlen, - data_len); - packetdata.buffer = sock->outbuf; - - /* Put the original packet into the buffer. */ - silc_buffer_put(sock->outbuf, data, data_len); - - /* Create the outgoing packet */ - silc_packet_assemble(&packetdata); - - /* Compute MAC of the packet. MAC is computed from the header, - padding and the relayed packet. */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - /* Encrypt the header and padding of the packet. This is encrypted - with normal session key shared with the client. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); - - SILC_LOG_HEXDUMP(("Packet to channel, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); - - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); - - /* We want to make sure that the packet is routed to same router - only once. Mark this route as sent route. */ - k = routed_count; - routed = silc_realloc(routed, sizeof(*routed) * (k + 1)); - routed[k] = client->router; - routed_count++; - continue; } - - /* XXX Check client's mode on the channel. */ + /* Remove the channel entry */ + if (silc_idlist_del_channel(server->local_list, channel)) + server->stat.my_channels--; + else + silc_idlist_del_channel(server->global_list, channel); + continue; + } - /* Get data used in packet header encryption, keys and stuff. */ - sock = (SilcSocketConnection)client->connection; - cipher = client->send_key; - hmac = client->hmac; - mac_len = hmac->hash->hash->hash_len; - hmac_key = client->hmac_key; - hmac_key_len = client->hmac_key_len; - - SILC_LOG_DEBUG(("Sending packet to client %s", - sock->hostname ? sock->hostname : sock->ip)); - - packetdata.truelen = data_len + SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len; - - /* Prepare outgoing data buffer for packet sending */ - silc_server_packet_send_prepare(server, sock, - SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + - packetdata.dst_id_len, - packetdata.padlen, - data_len); - packetdata.buffer = sock->outbuf; - - /* Put the original packet into the buffer. */ - silc_buffer_put(sock->outbuf, data, data_len); - - /* Create the outgoing packet */ - silc_packet_assemble(&packetdata); - - /* Compute MAC of the packet. MAC is computed from the header, - padding and the relayed packet. */ - silc_hmac_make_with_key(hmac, sock->outbuf->data, sock->outbuf->len, - hmac_key, hmac_key_len, mac); - silc_buffer_put_tail(sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - - /* Encrypt the header and padding of the packet. This is encrypted - with normal session key shared with the client. */ - silc_packet_encrypt(cipher, sock->outbuf, SILC_PACKET_HEADER_LEN + - packetdata.src_id_len + packetdata.dst_id_len + - packetdata.padlen); - - /* Pull MAC into the visible data area */ - silc_buffer_pull_tail(sock->outbuf, mac_len); + /* Send notify to channel about client leaving SILC and thus + the entire channel. */ + if (notify) + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_SIGNOFF, + signoff_message ? 2 : 1, + clidp->data, clidp->len, + signoff_message, signoff_message ? + strlen(signoff_message) : 0); + + if (keygen && !(channel->mode & SILC_CHANNEL_MODE_PRIVKEY)) { + /* Re-generate channel key */ + if (!silc_server_create_channel_key(server, channel, 0)) + return; - SILC_LOG_HEXDUMP(("Channel packet, len %d", sock->outbuf->len), - sock->outbuf->data, sock->outbuf->len); - - /* Now actually send the packet */ - silc_server_packet_send_real(server, sock, force_send); + /* Send the channel key to the channel. The key of course is not sent + to the client who was removed from the channel. */ + silc_server_send_channel_key(server, client->connection, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); } } - silc_free(packetdata.src_id); - silc_free(packetdata.dst_id); + silc_buffer_free(clidp); } -/* Relays received command reply packet to the correct destination. The - destination must be one of our locally connected client or the packet - will be ignored. This is called when server has forwarded one of - client's command request to router and router has now replied to the - command. */ +/* Removes client from one channel. This is used for example when client + calls LEAVE command to remove itself from the channel. Returns TRUE + if channel still exists and FALSE if the channel is removed when + last client leaves the channel. If `notify' is FALSE notify messages + are not sent. */ -void silc_server_packet_relay_command_reply(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet) +int silc_server_remove_from_one_channel(SilcServer server, + SilcSocketConnection sock, + SilcChannelEntry channel, + SilcClientEntry client, + int notify) { - SilcBuffer buffer = packet->buffer; - SilcClientList *client; - SilcClientID *id; - SilcSocketConnection dst_sock; - unsigned char mac[32]; - unsigned int mac_len = 0; + SilcChannelClientEntry chl; + SilcBuffer clidp; SILC_LOG_DEBUG(("Start")); - /* Source must be server or router */ - /* XXX: actually it must be only router */ - if (packet->src_id_type != SILC_ID_SERVER && - (sock->type != SILC_SOCKET_TYPE_SERVER || - sock->type != SILC_SOCKET_TYPE_ROUTER)) - goto out; - - /* Destination must be client */ - if (packet->dst_id_type != SILC_ID_CLIENT) - goto out; + /* Get the entry to the channel, if this client is not on the channel + then return Ok. */ + if (!silc_hash_table_find(client->channels, channel, NULL, (void *)&chl)) + return TRUE; + + /* Remove the client from the channel. The client is removed from + the channel's user list. */ + + clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + + /* Remove channel from client's channel list */ + silc_hash_table_del(client->channels, chl->channel); + + /* Remove channel if there is no users anymore */ + if (server->server_type == SILC_ROUTER && + silc_hash_table_count(channel->user_list) < 2) { + if (channel->rekey) + silc_schedule_task_del_by_context(server->schedule, channel->rekey); + if (silc_idlist_del_channel(server->local_list, channel)) + server->stat.my_channels--; + else + silc_idlist_del_channel(server->global_list, channel); + silc_buffer_free(clidp); + return FALSE; + } - /* Execute command reply locally for the command */ - silc_server_command_reply_process(server, sock, buffer); + /* Remove client from channel's client list */ + silc_hash_table_del(channel->user_list, chl->client); + + /* If there is no global users on the channel anymore mark the channel + as local channel. Do not check if the client is local client. */ + if (server->server_type != SILC_ROUTER && channel->global_users && + chl->client->router && !silc_server_channel_has_global(channel)) + channel->global_users = FALSE; + + silc_free(chl); + server->stat.my_chanclients--; + + /* If there is not at least one local user on the channel then we don't + need the channel entry anymore, we can remove it safely. */ + if (server->server_type != SILC_ROUTER && + !silc_server_channel_has_local(channel)) { + /* Notify about leaving client if this channel has global users. */ + if (notify && channel->global_users) + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_LEAVE, 1, + clidp->data, clidp->len); + + silc_buffer_free(clidp); + + if (channel->rekey) + silc_schedule_task_del_by_context(server->schedule, channel->rekey); - id = silc_id_str2id(packet->dst_id, SILC_ID_CLIENT); + if (channel->founder_key) { + /* The founder auth data exists, do not remove the channel entry */ + SilcChannelClientEntry chl2; + SilcHashTableList htl2; + + channel->disabled = TRUE; + + silc_hash_table_list(channel->user_list, &htl2); + while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) { + silc_hash_table_del(chl2->client->channels, channel); + silc_hash_table_del(channel->user_list, chl2->client); + silc_free(chl2); + } + return FALSE; + } - /* Destination must be one of ours */ - client = silc_idlist_find_client_by_id(server->local_list->clients, id); - if (!client) { - silc_free(id); - goto out; + /* Remove the channel entry */ + if (silc_idlist_del_channel(server->local_list, channel)) + server->stat.my_channels--; + else + silc_idlist_del_channel(server->global_list, channel); + return FALSE; } - /* Relay the packet to the client */ - if (client->hmac) - mac_len = client->hmac->hash->hash->hash_len; + /* Send notify to channel about client leaving the channel */ + if (notify) + silc_server_send_notify_to_channel(server, NULL, channel, FALSE, + SILC_NOTIFY_TYPE_LEAVE, 1, + clidp->data, clidp->len); - dst_sock = (SilcSocketConnection)client->connection; + silc_buffer_free(clidp); + return TRUE; +} - silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len - + packet->dst_id_len + packet->padlen); - silc_server_packet_send_prepare(server, dst_sock, 0, 0, buffer->len); - silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); - - /* Compute new HMAC */ - if (client->hmac) { - memset(mac, 0, sizeof(mac)); - silc_hmac_make_with_key(client->hmac, - dst_sock->outbuf->data, - dst_sock->outbuf->len, - client->hmac_key, - client->hmac_key_len, - mac); - silc_buffer_put_tail(dst_sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); +/* Timeout callback. This is called if connection is idle or for some + other reason is not responding within some period of time. This + disconnects the remote end. */ + +SILC_TASK_CALLBACK(silc_server_timeout_remote) +{ + SilcServer server = (SilcServer)context; + SilcSocketConnection sock = server->sockets[fd]; + + SILC_LOG_DEBUG(("Start")); + + if (!sock) + return; + + /* If we have protocol active we must assure that we call the protocol's + final callback so that all the memory is freed. */ + if (sock->protocol) { + sock->protocol->state = SILC_PROTOCOL_STATE_ERROR; + silc_protocol_execute_final(sock->protocol, server->schedule); + return; } - - /* Encrypt */ - if (client && client->send_key) - silc_packet_encrypt(client->send_key, dst_sock->outbuf, buffer->len); - - if (client->hmac) - silc_buffer_pull_tail(dst_sock->outbuf, mac_len); - - /* Send the packet */ - silc_server_packet_send_real(server, dst_sock, FALSE); - silc_free(id); + if (sock->user_data) + silc_server_free_sock_user_data(server, sock); - out: - silc_buffer_free(buffer); + silc_server_disconnect_remote(server, sock, "Server closed connection: " + "Connection timeout"); } -/* Closes connection to socket connection */ - -void silc_server_close_connection(SilcServer server, - SilcSocketConnection sock) +/* Creates new channel. Sends NEW_CHANNEL packet to primary route. This + function may be used only by router. In real SILC network all channels + are created by routers thus this function is never used by normal + server. */ + +SilcChannelEntry silc_server_create_new_channel(SilcServer server, + SilcServerID *router_id, + char *cipher, + char *hmac, + char *channel_name, + int broadcast) { + SilcChannelID *channel_id; + SilcChannelEntry entry; + SilcCipher key; + SilcHmac newhmac; - SILC_LOG_DEBUG(("Closing connection %d", sock->sock)); + SILC_LOG_DEBUG(("Creating new channel")); - /* We won't listen for this connection anymore */ - silc_schedule_unset_listen_fd(sock->sock); + if (!cipher) + cipher = SILC_DEFAULT_CIPHER; + if (!hmac) + hmac = SILC_DEFAULT_HMAC; - /* Unregister all tasks */ - silc_task_unregister_by_fd(server->io_queue, sock->sock); - silc_task_unregister_by_fd(server->timeout_queue, sock->sock); + /* Allocate cipher */ + if (!silc_cipher_alloc(cipher, &key)) + return NULL; - /* Close the actual connection */ - silc_net_close_connection(sock->sock); - server->sockets[sock->sock] = NULL; - silc_socket_free(sock); -} + /* Allocate hmac */ + if (!silc_hmac_alloc(hmac, NULL, &newhmac)) { + silc_cipher_free(key); + return NULL; + } -/* Sends disconnect message to remote connection and disconnects the - connection. */ + channel_name = strdup(channel_name); -void silc_server_disconnect_remote(SilcServer server, - SilcSocketConnection sock, - const char *fmt, ...) -{ - va_list ap; - unsigned char buf[4096]; + /* Create the channel */ + if (!silc_id_create_channel_id(server, router_id, server->rng, + &channel_id)) { + silc_free(channel_name); + silc_cipher_free(key); + silc_hmac_free(newhmac); + return NULL; + } + entry = silc_idlist_add_channel(server->local_list, channel_name, + SILC_CHANNEL_MODE_NONE, channel_id, + NULL, key, newhmac); + if (!entry) { + silc_free(channel_name); + silc_cipher_free(key); + silc_hmac_free(newhmac); + return NULL; + } - memset(buf, 0, sizeof(buf)); - va_start(ap, fmt); - vsprintf(buf, fmt, ap); - va_end(ap); + entry->cipher = strdup(cipher); + entry->hmac_name = strdup(hmac); + + /* Now create the actual key material */ + if (!silc_server_create_channel_key(server, entry, + silc_cipher_get_key_len(key) / 8)) { + silc_free(channel_name); + silc_cipher_free(key); + silc_hmac_free(newhmac); + silc_free(entry->cipher); + silc_free(entry->hmac_name); + return NULL; + } - SILC_LOG_DEBUG(("Disconnecting remote host")); + /* Notify other routers about the new channel. We send the packet + to our primary route. */ + if (broadcast && server->standalone == FALSE) + silc_server_send_new_channel(server, server->router->connection, TRUE, + channel_name, entry->id, + silc_id_get_len(entry->id, SILC_ID_CHANNEL), + entry->mode); - /* Notify remote end that the conversation is over. The notify message - is tried to be sent immediately. */ - silc_server_packet_send(server, sock, SILC_PACKET_DISCONNECT, 0, - buf, strlen(buf), TRUE); + server->stat.my_channels++; - /* Mark the connection to be disconnected */ - SILC_SET_DISCONNECTED(sock); - silc_server_close_connection(server, sock); + return entry; } -/* Free's user_data pointer from socket connection object. As this - pointer maybe anything we wil switch here to find the corrent - data type and free it the way it needs to be free'd. */ +/* Same as above but creates the channel with Channel ID `channel_id. */ -void silc_server_free_sock_user_data(SilcServer server, - SilcSocketConnection sock) +SilcChannelEntry +silc_server_create_new_channel_with_id(SilcServer server, + char *cipher, + char *hmac, + char *channel_name, + SilcChannelID *channel_id, + int broadcast) { - SILC_LOG_DEBUG(("Start")); + SilcChannelEntry entry; + SilcCipher key; + SilcHmac newhmac; -#define LCC(x) server->local_list->client_cache[(x) - 32] -#define LCCC(x) server->local_list->client_cache_count[(x) - 32] + SILC_LOG_DEBUG(("Creating new channel")); - switch(sock->type) { - case SILC_SOCKET_TYPE_CLIENT: - { - SilcClientList *user_data = (SilcClientList *)sock->user_data; + if (!cipher) + cipher = SILC_DEFAULT_CIPHER; + if (!hmac) + hmac = SILC_DEFAULT_HMAC; - /* Remove client from all channels */ - silc_server_remove_from_channels(server, sock, user_data); + /* Allocate cipher */ + if (!silc_cipher_alloc(cipher, &key)) + return NULL; - /* Clear ID cache */ - if (user_data->nickname && user_data->id) - silc_idcache_del_by_id(LCC(user_data->nickname[0]), - LCCC(user_data->nickname[0]), - SILC_ID_CLIENT, user_data->id); + /* Allocate hmac */ + if (!silc_hmac_alloc(hmac, NULL, &newhmac)) { + silc_cipher_free(key); + return NULL; + } - /* Free the client entry and everything in it */ - /* XXX must take some info to history before freeing */ - silc_idlist_del_client(&server->local_list->clients, user_data); - break; - } - case SILC_SOCKET_TYPE_SERVER: - case SILC_SOCKET_TYPE_ROUTER: - { + channel_name = strdup(channel_name); - break; - } - break; - default: - { - SilcIDListUnknown *user_data = (SilcIDListUnknown *)sock->user_data; - - if (user_data->send_key) - silc_cipher_free(user_data->send_key); - if (user_data->receive_key) - silc_cipher_free(user_data->receive_key); - if (user_data->pkcs) - silc_pkcs_free(user_data->pkcs); - if (user_data->hmac) { - silc_hmac_free(user_data->hmac); - memset(user_data->hmac_key, 0, user_data->hmac_key_len); - silc_free(user_data->hmac_key); - } - silc_free(user_data); - break; - } + /* Create the channel */ + entry = silc_idlist_add_channel(server->local_list, channel_name, + SILC_CHANNEL_MODE_NONE, channel_id, + NULL, key, newhmac); + if (!entry) { + silc_free(channel_name); + return NULL; } - sock->user_data = NULL; -#undef LCC -#undef LCCC -} + /* Now create the actual key material */ + if (!silc_server_create_channel_key(server, entry, + silc_cipher_get_key_len(key) / 8)) { + silc_free(channel_name); + return NULL; + } -/* Removes client from all channels it has joined. This is used when - client connection is disconnected. If the client on a channel - is last, the channel is removed as well. */ + /* Notify other routers about the new channel. We send the packet + to our primary route. */ + if (broadcast && server->standalone == FALSE) + silc_server_send_new_channel(server, server->router->connection, TRUE, + channel_name, entry->id, + silc_id_get_len(entry->id, SILC_ID_CHANNEL), + entry->mode); -void silc_server_remove_from_channels(SilcServer server, - SilcSocketConnection sock, - SilcClientList *client) -{ - int i, k; - SilcChannelList *channel; + server->stat.my_channels++; -#define LCC(x) server->local_list->channel_cache[(x) - 32] -#define LCCC(x) server->local_list->channel_cache_count[(x) - 32] + return entry; +} - /* Remove the client from all channels. The client is removed from - the channels' user list. */ - for (i = 0; i < client->channel_count; i++) { - channel = client->channel[i]; - if (!channel) - continue; +/* Channel's key re-key timeout callback. */ - /* Remove from channel */ - for (k = 0; k < channel->user_list_count; k++) { - if (channel->user_list[k].client == client) { - - /* If this client is last one on the channel the channel - is removed all together. */ - if (channel->user_list_count == 1) { - silc_idcache_del_by_id(LCC(channel->channel_name[0]), - LCCC(channel->channel_name[0]), - SILC_ID_CHANNEL, channel->id); - silc_idlist_del_channel(&server->local_list->channels, channel); - break; - } +SILC_TASK_CALLBACK(silc_server_channel_key_rekey) +{ + SilcServerChannelRekey rekey = (SilcServerChannelRekey)context; + SilcServer server = (SilcServer)rekey->context; - channel->user_list[k].client = NULL; - channel->user_list[k].mode = SILC_CHANNEL_UMODE_NONE; + rekey->task = NULL; - /* XXX */ - /* Send notify to channel about client leaving SILC and thus - the entire channel. */ - silc_server_send_notify_to_channel(server, channel, - "%s has left channel %s", - client->nickname, - channel->channel_name); - } - } - } + if (!silc_server_create_channel_key(server, rekey->channel, rekey->key_len)) + return; - if (client->channel_count) - silc_free(client->channel); - client->channel = NULL; -#undef LCC -#undef LCCC + silc_server_send_channel_key(server, NULL, rekey->channel, FALSE); } -/* Timeout callback. This is called if connection is idle or for some - other reason is not responding within some period of time. This - disconnects the remote end. */ +/* Generates new channel key. This is used to create the initial channel key + but also to re-generate new key for channel. If `key_len' is provided + it is the bytes of the key length. */ -SILC_TASK_CALLBACK(silc_server_timeout_remote) +bool silc_server_create_channel_key(SilcServer server, + SilcChannelEntry channel, + uint32 key_len) { - SilcServer server = (SilcServer)context; - SilcSocketConnection sock = server->sockets[fd]; + int i; + unsigned char channel_key[32], hash[32]; + uint32 len; - silc_server_disconnect_remote(server, sock, - "Server closed connection: " - "Connection timeout"); -} + SILC_LOG_DEBUG(("Generating channel key")); -/* Internal routine used to send (relay, route) private messages to some - destination. This is used to by normal server to send the message to - its primary route and router uses this to send it to any route it - wants. If the private message key does not exist then the message - is re-encrypted, otherwise we just pass it along. */ -static void -silc_server_private_message_send_internal(SilcServer server, - SilcSocketConnection dst_sock, - SilcServerList *router, - SilcPacketContext *packet) -{ - SilcBuffer buffer = packet->buffer; + if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) { + SILC_LOG_DEBUG(("Channel has private keys, will not generate new key")); + return TRUE; + } - /* Send and re-encrypt if private messge key does not exist */ - if ((packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY) == FALSE) { - unsigned char mac[32]; - unsigned int mac_len = 0; - - if (router->hmac) - mac_len = router->hmac->hash->hash->hash_len; - - silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len - + packet->dst_id_len + packet->padlen); - silc_server_packet_send_prepare(server, dst_sock, 0, 0, buffer->len); - silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); - - /* Compute new HMAC */ - if (router->hmac) { - mac_len = router->hmac->hash->hash->hash_len; - memset(mac, 0, sizeof(mac)); - silc_hmac_make_with_key(router->hmac, - dst_sock->outbuf->data, - dst_sock->outbuf->len, - router->hmac_key, - router->hmac_key_len, - mac); - silc_buffer_put_tail(dst_sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - } - - silc_packet_encrypt(router->send_key, dst_sock->outbuf, buffer->len); - - if (router->hmac) - silc_buffer_pull_tail(dst_sock->outbuf, mac_len); - - /* Send the packet */ - silc_server_packet_send_real(server, dst_sock, FALSE); + if (!channel->channel_key) + if (!silc_cipher_alloc(SILC_DEFAULT_CIPHER, &channel->channel_key)) + return FALSE; - } else { - /* Key exist so just send it */ - silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len - + packet->dst_id_len + packet->padlen); - silc_server_packet_send_prepare(server, dst_sock, 0, 0, buffer->len); - silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); - silc_server_packet_send_real(server, dst_sock, FALSE); + if (key_len) + len = key_len; + else if (channel->key_len) + len = channel->key_len / 8; + else + len = silc_cipher_get_key_len(channel->channel_key) / 8; + + /* Create channel key */ + for (i = 0; i < len; i++) channel_key[i] = silc_rng_get_byte(server->rng); + + /* Set the key */ + silc_cipher_set_key(channel->channel_key, channel_key, len * 8); + + /* Remove old key if exists */ + if (channel->key) { + memset(channel->key, 0, channel->key_len / 8); + silc_free(channel->key); } -} -/* Internal routine to send the received private message packet to - our locally connected client. */ -static void -silc_server_private_message_send_local(SilcServer server, - SilcSocketConnection dst_sock, - SilcClientList *client, - SilcPacketContext *packet) -{ - SilcBuffer buffer = packet->buffer; + /* Save the key */ + channel->key_len = len * 8; + channel->key = silc_calloc(len, sizeof(*channel->key)); + memcpy(channel->key, channel_key, len); + memset(channel_key, 0, sizeof(channel_key)); - /* Re-encrypt packet if needed */ - if ((packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY) == FALSE) { - unsigned char mac[32]; - unsigned int mac_len = 0; + /* Generate HMAC key from the channel key data and set it */ + if (!channel->hmac) + silc_hmac_alloc(SILC_DEFAULT_HMAC, NULL, &channel->hmac); + silc_hash_make(silc_hmac_get_hash(channel->hmac), channel->key, len, hash); + silc_hmac_set_key(channel->hmac, hash, + silc_hash_len(silc_hmac_get_hash(channel->hmac))); + memset(hash, 0, sizeof(hash)); - if (client->hmac) - mac_len = client->hmac->hash->hash->hash_len; - - silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len - + packet->dst_id_len + packet->padlen); - silc_server_packet_send_prepare(server, dst_sock, 0, 0, buffer->len); - silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); - - /* Compute new HMAC */ - if (client->hmac) { - memset(mac, 0, sizeof(mac)); - silc_hmac_make_with_key(client->hmac, - dst_sock->outbuf->data, - dst_sock->outbuf->len, - client->hmac_key, - client->hmac_key_len, - mac); - silc_buffer_put_tail(dst_sock->outbuf, mac, mac_len); - memset(mac, 0, sizeof(mac)); - } - - /* Encrypt */ - if (client && client->send_key) - silc_packet_encrypt(client->send_key, dst_sock->outbuf, - buffer->len); - - if (client->hmac) - silc_buffer_pull_tail(dst_sock->outbuf, mac_len); - - /* Send the packet */ - silc_server_packet_send_real(server, dst_sock, FALSE); - } else { - /* Key exist so just send it */ - silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len - + packet->dst_id_len + packet->padlen); - silc_server_packet_send_prepare(server, dst_sock, 0, 0, buffer->len); - silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len); - silc_server_packet_send_real(server, dst_sock, FALSE); + if (server->server_type == SILC_ROUTER) { + if (!channel->rekey) + channel->rekey = silc_calloc(1, sizeof(*channel->rekey)); + channel->rekey->context = (void *)server; + channel->rekey->channel = channel; + channel->rekey->key_len = key_len; + if (channel->rekey->task) + silc_schedule_task_del(server->schedule, channel->rekey->task); + + channel->rekey->task = + silc_schedule_task_add(server->schedule, 0, + silc_server_channel_key_rekey, + (void *)channel->rekey, 3600, 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); } + + return TRUE; } -/* Received private message. This resolves the destination of the message - and sends the packet. This is used by both server and router. If the - destination is our locally connected client this sends the packet to - the client. This may also send the message for further routing if - the destination is not in our server (or router). */ +/* Saves the channel key found in the encoded `key_payload' buffer. This + function is used when we receive Channel Key Payload and also when we're + processing JOIN command reply. Returns entry to the channel. */ -void silc_server_private_message(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet) +SilcChannelEntry silc_server_save_channel_key(SilcServer server, + SilcBuffer key_payload, + SilcChannelEntry channel) { - SilcBuffer buffer = packet->buffer; - SilcClientID *id; - SilcServerList *router; - SilcSocketConnection dst_sock; - SilcClientList *client; + SilcChannelKeyPayload payload = NULL; + SilcChannelID *id = NULL; + unsigned char *tmp, hash[32]; + uint32 tmp_len; + char *cipher; SILC_LOG_DEBUG(("Start")); - if (!packet->dst_id) { - SILC_LOG_DEBUG(("Bad Client ID in private message packet")); - goto err; - } - - /* Decode destination Client ID */ - id = silc_id_str2id(packet->dst_id, SILC_ID_CLIENT); - if (!id) { - SILC_LOG_DEBUG(("Could not decode destination Client ID")); - goto err; + /* Decode channel key payload */ + payload = silc_channel_key_payload_parse(key_payload); + if (!payload) { + SILC_LOG_ERROR(("Bad channel key payload, dropped")); + channel = NULL; + goto out; } - /* If the destination belongs to our server we don't have to route - the message anywhere but to send it to the local destination. */ - /* XXX: Should use local cache to search but the current idcache system - is so sucky that it cannot be used... it MUST be rewritten! Using - this search is probably faster than if we'd use here the current - idcache system. */ - client = silc_idlist_find_client_by_id(server->local_list->clients, id); - if (client) { - /* It exists, now deliver the message to the destination */ - dst_sock = (SilcSocketConnection)client->connection; + /* Get the channel entry */ + if (!channel) { - /* If we are router and the client has router then the client is in - our cell but not directly connected to us. */ - if (server->server_type == SILC_ROUTER && client->router) { - silc_server_private_message_send_internal(server, dst_sock, - client->router, packet); + /* Get channel ID */ + tmp = silc_channel_key_get_id(payload, &tmp_len); + id = silc_id_str2id(tmp, tmp_len, SILC_ID_CHANNEL); + if (!id) { + channel = NULL; goto out; } - /* Seems that client really is directly connected to us */ - silc_server_private_message_send_local(server, dst_sock, client, packet); + channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL); + if (!channel) { + channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL); + if (!channel) { + SILC_LOG_ERROR(("Received key for non-existent channel")); + goto out; + } + } + } + + tmp = silc_channel_key_get_key(payload, &tmp_len); + if (!tmp) { + channel = NULL; goto out; } - /* Destination belongs to someone not in this server. If we are normal - server our action is to send the packet to our router. */ - if (server->server_type == SILC_SERVER && !server->standalone) { - router = server->id_entry->router; - dst_sock = (SilcSocketConnection)router->connection; + cipher = silc_channel_key_get_cipher(payload, NULL); + if (!cipher) { + channel = NULL; + goto out; + } + + /* Remove old key if exists */ + if (channel->key) { + memset(channel->key, 0, channel->key_len / 8); + silc_free(channel->key); + silc_cipher_free(channel->channel_key); + } - /* Send to primary route */ - silc_server_private_message_send_internal(server, dst_sock, router, - packet); + /* Create new cipher */ + if (!silc_cipher_alloc(cipher, &channel->channel_key)) { + channel = NULL; goto out; } - /* We are router and we will perform route lookup for the destination - and send the message to the correct route. */ - if (server->server_type == SILC_ROUTER && !server->standalone) { + if (channel->cipher) + silc_free(channel->cipher); + channel->cipher = strdup(cipher); - /* If we don't have specific route for the destination we will send - it to our primary route (default route). */ - router = silc_server_route_check(id->ip.s_addr, server->id->port); - if (router) { - dst_sock = (SilcSocketConnection)router->connection; - } else { - router = server->id_entry->router; - dst_sock = (SilcSocketConnection)router->connection; - } + /* Save the key */ + channel->key_len = tmp_len * 8; + channel->key = silc_calloc(tmp_len, sizeof(unsigned char)); + memcpy(channel->key, tmp, tmp_len); + silc_cipher_set_key(channel->channel_key, tmp, channel->key_len); - /* Send packet */ - silc_server_private_message_send_internal(server, dst_sock, - router, packet); - goto out; + /* Generate HMAC key from the channel key data and set it */ + if (!channel->hmac) + silc_hmac_alloc(SILC_DEFAULT_HMAC, NULL, &channel->hmac); + silc_hash_make(silc_hmac_get_hash(channel->hmac), tmp, tmp_len, hash); + silc_hmac_set_key(channel->hmac, hash, + silc_hash_len(silc_hmac_get_hash(channel->hmac))); + + memset(hash, 0, sizeof(hash)); + memset(tmp, 0, tmp_len); + + if (server->server_type == SILC_ROUTER) { + if (!channel->rekey) + channel->rekey = silc_calloc(1, sizeof(*channel->rekey)); + channel->rekey->context = (void *)server; + channel->rekey->channel = channel; + if (channel->rekey->task) + silc_schedule_task_del(server->schedule, channel->rekey->task); + + channel->rekey->task = + silc_schedule_task_add(server->schedule, 0, + silc_server_channel_key_rekey, + (void *)channel->rekey, 3600, 0, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); } - err: - silc_server_send_error(server, sock, - "No such nickname: Private message not sent"); out: - silc_buffer_free(buffer); + silc_free(id); + if (payload) + silc_channel_key_payload_free(payload); + + return channel; } -SilcChannelList *silc_find_channel(SilcServer server, SilcChannelID *id) +/* Heartbeat callback. This function is set as argument for the + silc_socket_set_heartbeat function. The library will call this function + at the set time interval. */ + +void silc_server_perform_heartbeat(SilcSocketConnection sock, + void *hb_context) { - int i; - SilcIDCache *id_cache; + SilcServerHBContext hb = (SilcServerHBContext)hb_context; -#define LCC(x) server->local_list->channel_cache[(x)] -#define LCCC(x) server->local_list->channel_cache_count[(x)] + SILC_LOG_DEBUG(("Sending heartbeat to %s (%s)", sock->hostname, + sock->ip)); - for (i = 0; i < 96; i++) { - if (LCC(i) == NULL) - continue; - if (silc_idcache_find_by_id(LCC(i), LCCC(i), (void *)id, - SILC_ID_CHANNEL, &id_cache)) - return (SilcChannelList *)id_cache->context; - } - - return NULL; -#undef LCC -#undef LCCC + /* Send the heartbeat */ + silc_server_send_heartbeat(hb->server, sock); } -/* Process received channel message. */ +/* Returns assembled of all servers in the given ID list. The packet's + form is dictated by the New ID payload. */ -void silc_server_channel_message(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet) +static void silc_server_announce_get_servers(SilcServer server, + SilcServerEntry remote, + SilcIDList id_list, + SilcBuffer *servers, + unsigned long creation_time) { - SilcChannelList *channel = NULL; - SilcChannelID *id = NULL; - SilcClientID *sender; - SilcBuffer buffer = packet->buffer; - - SILC_LOG_DEBUG(("Processing channel message")); - - /* Check MAC */ - if (!silc_server_packet_check_mac(server, sock, buffer)) - goto out; + SilcIDCacheList list; + SilcIDCacheEntry id_cache; + SilcServerEntry entry; + SilcBuffer idp; + + /* Go through all clients in the list */ + if (silc_idcache_get_all(id_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + entry = (SilcServerEntry)id_cache->context; + + /* Do not announce the one we've sending our announcements and + do not announce ourself. Also check the creation time if it's + provided. */ + if ((entry == remote) || (entry == server->id_entry) || + (creation_time && entry->data.created < creation_time)) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + continue; + } - /* Sanity checks */ - if (packet->dst_id_type != SILC_ID_CHANNEL) { - SILC_LOG_ERROR(("Received bad message for channel, dropped")); - SILC_LOG_DEBUG(("Received bad message for channel, dropped")); - goto out; - } + idp = silc_id_payload_encode(entry->id, SILC_ID_SERVER); - /* Send to local clients */ - id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL); - channel = silc_find_channel(server, id); - if (!channel) { - SILC_LOG_DEBUG(("Could not find channel")); - goto out; - } + *servers = silc_buffer_realloc(*servers, + (*servers ? + (*servers)->truelen + idp->len : + idp->len)); + silc_buffer_pull_tail(*servers, ((*servers)->end - (*servers)->data)); + silc_buffer_put(*servers, idp->data, idp->len); + silc_buffer_pull(*servers, idp->len); + silc_buffer_free(idp); - /* Distribute the packet to our local clients. This will send the - packet for further routing as well, if needed. */ - sender = silc_id_str2id(packet->src_id, packet->src_id_type); - silc_server_packet_relay_to_channel(server, sock, channel, sender, - packet->src_id_type, - packet->buffer->data, - packet->buffer->len, FALSE); + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } - out: - silc_buffer_free(buffer); + silc_idcache_list_free(list); + } } -/* Received channel key packet. We distribute the key to all of our locally - connected clients on the channel. Router ignores the packet. */ +/* This function is used by router to announce existing servers to our + primary router when we've connected to it. If `creation_time' is non-zero + then only the servers that has been created after the `creation_time' + will be announced. */ -void silc_server_channel_key(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet) +void silc_server_announce_servers(SilcServer server, bool global, + unsigned long creation_time, + SilcSocketConnection remote) { - SilcBuffer buffer = packet->buffer; - SilcChannelKeyPayload payload = NULL; - SilcChannelID *id = NULL; - SilcChannelList *channel; - SilcClientList *client; - unsigned char *key; - unsigned int key_len; - char *cipher; - int i; + SilcBuffer servers = NULL; - if (server->server_type == SILC_ROUTER) - goto out; + SILC_LOG_DEBUG(("Announcing servers")); - if (packet->src_id_type != SILC_ID_SERVER && - sock->type != SILC_SOCKET_TYPE_ROUTER) - goto out; + /* Get servers in local list */ + silc_server_announce_get_servers(server, remote->user_data, + server->local_list, &servers, + creation_time); - /* Decode channel key payload */ - payload = silc_channel_key_parse_payload(buffer); - if (!payload) { - SILC_LOG_ERROR(("Bad channel key payload, dropped")); - SILC_LOG_DEBUG(("Bad channel key payload, dropped")); - } + if (global) + /* Get servers in global list */ + silc_server_announce_get_servers(server, remote->user_data, + server->global_list, &servers, + creation_time); - /* Get channel ID */ - id = silc_id_str2id(silc_channel_key_get_id(payload, NULL), SILC_ID_CHANNEL); - if (!id) - goto out; + if (servers) { + silc_buffer_push(servers, servers->data - servers->head); + SILC_LOG_HEXDUMP(("servers"), servers->data, servers->len); - /* Get the channel entry */ - channel = silc_idlist_find_channel_by_id(server->local_list->channels, id); - if (!channel) { - SILC_LOG_ERROR(("Received key for non-existent channel")); - SILC_LOG_DEBUG(("Received key for non-existent channel")); - goto out; + /* Send the packet */ + silc_server_packet_send(server, remote, + SILC_PACKET_NEW_ID, SILC_PACKET_FLAG_LIST, + servers->data, servers->len, TRUE); + + silc_buffer_free(servers); } +} - /* Save the key for us as well */ - key = silc_channel_key_get_key(payload, &key_len); - if (!key) - goto out; - cipher = silc_channel_key_get_cipher(payload, NULL);; - if (!cipher) - goto out; - channel->key_len = key_len; - channel->key = silc_calloc(key_len, sizeof(unsigned char)); - memcpy(channel->key, key, key_len); - silc_cipher_alloc(cipher, &channel->channel_key); - channel->channel_key->cipher->set_key(channel->channel_key->context, - key, key_len); +/* Returns assembled packet of all clients in the given ID list. The + packet's form is dictated by the New ID Payload. */ - /* Distribute the key to all clients on the channel */ - for (i = 0; i < channel->user_list_count; i++) { - client = channel->user_list[i].client; +static void silc_server_announce_get_clients(SilcServer server, + SilcIDList id_list, + SilcBuffer *clients, + unsigned long creation_time) +{ + SilcIDCacheList list; + SilcIDCacheEntry id_cache; + SilcClientEntry client; + SilcBuffer idp; + + /* Go through all clients in the list */ + if (silc_idcache_get_all(id_list->clients, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + client = (SilcClientEntry)id_cache->context; + + if (creation_time && client->data.created < creation_time) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + continue; + } - if (client) - silc_server_packet_send_dest(server, client->connection, - SILC_PACKET_CHANNEL_KEY, 0, - client->id, SILC_ID_CLIENT, - buffer->data, buffer->len, FALSE); - } + idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); - out: - if (id) - silc_free(id); - if (payload) - silc_channel_key_free_payload(payload); - silc_buffer_free(buffer); + *clients = silc_buffer_realloc(*clients, + (*clients ? + (*clients)->truelen + idp->len : + idp->len)); + silc_buffer_pull_tail(*clients, ((*clients)->end - (*clients)->data)); + silc_buffer_put(*clients, idp->data, idp->len); + silc_buffer_pull(*clients, idp->len); + silc_buffer_free(idp); + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + + silc_idcache_list_free(list); + } } -/* Sends error message. Error messages may or may not have any - implications. */ +/* This function is used to announce our existing clients to our router + when we've connected to it. If `creation_time' is non-zero then only + the clients that has been created after the `creation_time' will be + announced. */ -void silc_server_send_error(SilcServer server, - SilcSocketConnection sock, - const char *fmt, ...) +void silc_server_announce_clients(SilcServer server, + unsigned long creation_time, + SilcSocketConnection remote) { - va_list ap; - unsigned char buf[4096]; + SilcBuffer clients = NULL; - memset(buf, 0, sizeof(buf)); - va_start(ap, fmt); - vsprintf(buf, fmt, ap); - va_end(ap); + SILC_LOG_DEBUG(("Announcing clients")); - silc_server_packet_send(server, sock, SILC_PACKET_ERROR, 0, - buf, strlen(buf), FALSE); -} + /* Get clients in local list */ + silc_server_announce_get_clients(server, server->local_list, + &clients, creation_time); -/* Sends notify message */ + /* As router we announce our global list as well */ + if (server->server_type == SILC_ROUTER) + silc_server_announce_get_clients(server, server->global_list, + &clients, creation_time); -void silc_server_send_notify(SilcServer server, - SilcSocketConnection sock, - const char *fmt, ...) -{ - va_list ap; - unsigned char buf[4096]; + if (clients) { + silc_buffer_push(clients, clients->data - clients->head); + SILC_LOG_HEXDUMP(("clients"), clients->data, clients->len); - memset(buf, 0, sizeof(buf)); - va_start(ap, fmt); - vsprintf(buf, fmt, ap); - va_end(ap); + /* Send the packet */ + silc_server_packet_send(server, remote, + SILC_PACKET_NEW_ID, SILC_PACKET_FLAG_LIST, + clients->data, clients->len, TRUE); - silc_server_packet_send(server, sock, SILC_PACKET_NOTIFY, 0, - buf, strlen(buf), FALSE); + silc_buffer_free(clients); + } } -/* Sends notify message to a channel. The notify message sent is - distributed to all clients on the channel. */ - -void silc_server_send_notify_to_channel(SilcServer server, - SilcChannelList *channel, - const char *fmt, ...) +static SilcBuffer +silc_server_announce_encode_notify(SilcNotifyType notify, uint32 argc, ...) { va_list ap; - unsigned char buf[4096]; + SilcBuffer p; - memset(buf, 0, sizeof(buf)); - va_start(ap, fmt); - vsprintf(buf, fmt, ap); + va_start(ap, argc); + p = silc_notify_payload_encode(notify, argc, ap); va_end(ap); - - silc_server_packet_send_to_channel(server, channel, buf, - strlen(buf), FALSE); + + return p; } -/* Sends New ID Payload to remote end. The packet is used to distribute - information about new registered clients, servers, channel etc. usually - to routers so that they can keep these information up to date. - If the argument `broadcast' is TRUE then the packet is sent as - broadcast packet. */ - -void silc_server_send_new_id(SilcServer server, - SilcSocketConnection sock, - int broadcast, - void *id, SilcIdType id_type, - unsigned int id_len) +/* Returns assembled packets for channel users of the `channel'. */ + +void silc_server_announce_get_channel_users(SilcServer server, + SilcChannelEntry channel, + SilcBuffer *channel_users, + SilcBuffer *channel_users_modes) { - SilcBuffer packet; - unsigned char *id_string; + SilcChannelClientEntry chl; + SilcHashTableList htl; + SilcBuffer chidp, clidp; + SilcBuffer tmp; + int len; + unsigned char mode[4]; - id_string = silc_id_id2str(id, id_type); - if (!id_string) - return; + SILC_LOG_DEBUG(("Start")); - packet = silc_buffer_alloc(2 + 2 + id_len); - silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); - silc_buffer_format(packet, - SILC_STR_UI_SHORT(id_type), - SILC_STR_UI_SHORT(id_len), - SILC_STR_UI_XNSTRING(id_string, id_len), - SILC_STR_END); + /* Now find all users on the channel */ + chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL); + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + clidp = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT); + + /* JOIN Notify */ + tmp = silc_server_announce_encode_notify(SILC_NOTIFY_TYPE_JOIN, 2, + clidp->data, clidp->len, + chidp->data, chidp->len); + len = tmp->len; + *channel_users = + silc_buffer_realloc(*channel_users, + (*channel_users ? + (*channel_users)->truelen + len : len)); + silc_buffer_pull_tail(*channel_users, + ((*channel_users)->end - + (*channel_users)->data)); + + silc_buffer_put(*channel_users, tmp->data, tmp->len); + silc_buffer_pull(*channel_users, len); + silc_buffer_free(tmp); + + /* CUMODE notify for mode change on the channel */ + SILC_PUT32_MSB(chl->mode, mode); + tmp = silc_server_announce_encode_notify(SILC_NOTIFY_TYPE_CUMODE_CHANGE, + 3, clidp->data, clidp->len, + mode, 4, + clidp->data, clidp->len); + len = tmp->len; + *channel_users_modes = + silc_buffer_realloc(*channel_users_modes, + (*channel_users_modes ? + (*channel_users_modes)->truelen + len : len)); + silc_buffer_pull_tail(*channel_users_modes, + ((*channel_users_modes)->end - + (*channel_users_modes)->data)); + + silc_buffer_put(*channel_users_modes, tmp->data, tmp->len); + silc_buffer_pull(*channel_users_modes, len); + silc_buffer_free(tmp); - silc_server_packet_send(server, sock, SILC_PACKET_NEW_ID, - broadcast ? SILC_PACKET_FLAG_BROADCAST : 0, - packet->data, packet->len, FALSE); - silc_free(id_string); - silc_buffer_free(packet); + silc_buffer_free(clidp); + } + silc_buffer_free(chidp); } -/* Sends Replace ID payload to remote end. This is used to replace old - ID with new ID sent in the packet. This is called for example when - user changes nickname and we create new ID for the user. If the - argument `broadcast' is TRUE then the packet is sent as - broadcast packet. */ -/* XXX It would be expected that the new id is same type as the old - ID. :) */ - -void silc_server_send_replace_id(SilcServer server, - SilcSocketConnection sock, - int broadcast, - void *old_id, SilcIdType old_id_type, - unsigned int old_id_len, - void *new_id, SilcIdType new_id_type, - unsigned int new_id_len) +/* Returns assembled packets for all channels and users on those channels + from the given ID List. The packets are in the form dictated by the + New Channel and New Channel User payloads. */ + +void silc_server_announce_get_channels(SilcServer server, + SilcIDList id_list, + SilcBuffer *channels, + SilcBuffer *channel_users, + SilcBuffer **channel_users_modes, + uint32 *channel_users_modes_c, + SilcChannelID ***channel_ids, + unsigned long creation_time) { - SilcBuffer packet; - unsigned char *oid; - unsigned char *nid; + SilcIDCacheList list; + SilcIDCacheEntry id_cache; + SilcChannelEntry channel; + unsigned char *cid; + uint32 id_len; + uint16 name_len; + int len; + int i = *channel_users_modes_c; + bool announce; - oid = silc_id_id2str(old_id, old_id_type); - if (!oid) - return; + SILC_LOG_DEBUG(("Start")); - nid = silc_id_id2str(new_id, new_id_type); - if (!nid) - return; + /* Go through all channels in the list */ + if (silc_idcache_get_all(id_list->channels, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + channel = (SilcChannelEntry)id_cache->context; + + if (creation_time && channel->created < creation_time) + announce = FALSE; + else + announce = TRUE; + + cid = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + id_len = silc_id_get_len(channel->id, SILC_ID_CHANNEL); + name_len = strlen(channel->channel_name); + + if (announce) { + len = 4 + name_len + id_len + 4; + *channels = + silc_buffer_realloc(*channels, + (*channels ? (*channels)->truelen + + len : len)); + silc_buffer_pull_tail(*channels, + ((*channels)->end - (*channels)->data)); + silc_buffer_format(*channels, + SILC_STR_UI_SHORT(name_len), + SILC_STR_UI_XNSTRING(channel->channel_name, + name_len), + SILC_STR_UI_SHORT(id_len), + SILC_STR_UI_XNSTRING(cid, id_len), + SILC_STR_UI_INT(channel->mode), + SILC_STR_END); + silc_buffer_pull(*channels, len); + } - packet = silc_buffer_alloc(2 + 2 + 2 + 2 + old_id_len + new_id_len); - silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); - silc_buffer_format(packet, - SILC_STR_UI_SHORT(old_id_type), - SILC_STR_UI_SHORT(old_id_len), - SILC_STR_UI_XNSTRING(oid, old_id_len), - SILC_STR_UI_SHORT(new_id_type), - SILC_STR_UI_SHORT(new_id_len), - SILC_STR_UI_XNSTRING(nid, new_id_len), - SILC_STR_END); + *channel_users_modes = silc_realloc(*channel_users_modes, + sizeof(**channel_users_modes) * + (i + 1)); + (*channel_users_modes)[i] = NULL; + *channel_ids = silc_realloc(*channel_ids, + sizeof(**channel_ids) * (i + 1)); + (*channel_ids)[i] = NULL; + silc_server_announce_get_channel_users(server, channel, + channel_users, + channel_users_modes[i]); + (*channel_ids)[i] = channel->id; + i++; + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } - silc_server_packet_send(server, sock, SILC_PACKET_REPLACE_ID, - broadcast ? SILC_PACKET_FLAG_BROADCAST : 0, - packet->data, packet->len, FALSE); - silc_free(oid); - silc_free(nid); - silc_buffer_free(packet); + *channel_users_modes_c += i; + } + + silc_idcache_list_free(list); + } } -/* Creates new channel. */ +/* This function is used to announce our existing channels to our router + when we've connected to it. This also announces the users on the + channels to the router. If the `creation_time' is non-zero only the + channels that was created after the `creation_time' are announced. + Note that the channel users are still announced even if the `creation_time' + was provided. */ -SilcChannelList *silc_server_new_channel(SilcServer server, - SilcServerID *router_id, - char *cipher, char *channel_name) +void silc_server_announce_channels(SilcServer server, + unsigned long creation_time, + SilcSocketConnection remote) { - int i, channel_len; - SilcChannelID *channel_id; - SilcChannelList *entry; - SilcCipher key; - unsigned char channel_key[32], *id_string; - SilcBuffer packet; + SilcBuffer channels = NULL, channel_users = NULL; + SilcBuffer *channel_users_modes = NULL; + uint32 channel_users_modes_c = 0; + SilcChannelID **channel_ids = NULL; + + SILC_LOG_DEBUG(("Announcing channels and channel users")); + + /* Get channels and channel users in local list */ + silc_server_announce_get_channels(server, server->local_list, + &channels, &channel_users, + &channel_users_modes, + &channel_users_modes_c, + &channel_ids, creation_time); + + /* Get channels and channel users in global list */ + silc_server_announce_get_channels(server, server->global_list, + &channels, &channel_users, + &channel_users_modes, + &channel_users_modes_c, + &channel_ids, creation_time); + + if (channels) { + silc_buffer_push(channels, channels->data - channels->head); + SILC_LOG_HEXDUMP(("channels"), channels->data, channels->len); - SILC_LOG_DEBUG(("Creating new channel")); + /* Send the packet */ + silc_server_packet_send(server, remote, + SILC_PACKET_NEW_CHANNEL, SILC_PACKET_FLAG_LIST, + channels->data, channels->len, + FALSE); -#define LCC(x) server->local_list->channel_cache[(x) - 32] -#define LCCC(x) server->local_list->channel_cache_count[(x) - 32] + silc_buffer_free(channels); + } - /* Create channel key */ - for (i = 0; i < 32; i++) - channel_key[i] = silc_rng_get_byte(server->rng); + if (channel_users) { + silc_buffer_push(channel_users, channel_users->data - channel_users->head); + SILC_LOG_HEXDUMP(("channel users"), channel_users->data, + channel_users->len); - if (!cipher) - cipher = "twofish"; + /* Send the packet */ + silc_server_packet_send(server, remote, + SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST, + channel_users->data, channel_users->len, + FALSE); - /* Allocate keys */ - silc_cipher_alloc(cipher, &key); - key->cipher->set_key(key->context, channel_key, 16); + silc_buffer_free(channel_users); + } - /* Create the channel */ - silc_id_create_channel_id(router_id, server->rng, &channel_id); - silc_idlist_add_channel(&server->local_list->channels, channel_name, - SILC_CHANNEL_MODE_NONE, channel_id, NULL, /*XXX*/ - key, &entry); - LCCC(channel_name[0]) = silc_idcache_add(&LCC(channel_name[0]), - LCCC(channel_name[0]), - channel_name, SILC_ID_CHANNEL, - channel_id, (void *)entry); - entry->key = silc_calloc(16, sizeof(*entry->key)); - entry->key_len = 16; - memcpy(entry->key, channel_key, 16); - memset(channel_key, 0, sizeof(channel_key)); + if (channel_users_modes) { + int i; + + for (i = 0; i < channel_users_modes_c; i++) { + silc_buffer_push(channel_users_modes[i], + channel_users_modes[i]->data - + channel_users_modes[i]->head); + SILC_LOG_HEXDUMP(("channel users modes"), channel_users_modes[i]->data, + channel_users_modes[i]->len); + silc_server_packet_send_dest(server, remote, + SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST, + channel_ids[i], SILC_ID_CHANNEL, + channel_users_modes[i]->data, + channel_users_modes[i]->len, + FALSE); + silc_buffer_free(channel_users_modes[i]); + } + silc_free(channel_users_modes); + silc_free(channel_ids); + } +} - /* Notify other routers about the new channel. We send the packet - to our primary route. */ - if (server->standalone == FALSE) { - channel_len = strlen(channel_name); - id_string = silc_id_id2str(entry->id, SILC_ID_CHANNEL); - packet = silc_buffer_alloc(2 + SILC_ID_CHANNEL_LEN); - - silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); - silc_buffer_format(packet, - SILC_STR_UI_SHORT(channel_len), - SILC_STR_UI_XNSTRING(channel_name, channel_len), - SILC_STR_UI_SHORT(SILC_ID_CHANNEL_LEN), - SILC_STR_UI_XNSTRING(id_string, SILC_ID_CHANNEL_LEN), - SILC_STR_END); +/* Failure timeout callback. If this is called then we will immediately + process the received failure. We always process the failure with timeout + since we do not want to blindly trust to received failure packets. + This won't be called (the timeout is cancelled) if the failure was + bogus (it is bogus if remote does not close the connection after sending + the failure). */ - /* Send the packet to our router. */ - silc_server_packet_send(server, (SilcSocketConnection) - server->id_entry->router->connection, - SILC_PACKET_NEW_CHANNEL_USER, 0, - packet->data, packet->len, TRUE); - - silc_free(id_string); - silc_buffer_free(packet); +SILC_TASK_CALLBACK(silc_server_failure_callback) +{ + SilcServerFailureContext f = (SilcServerFailureContext)context; + + if (f->sock->protocol) { + f->sock->protocol->state = SILC_PROTOCOL_STATE_FAILURE; + silc_protocol_execute(f->sock->protocol, f->server->schedule, 0, 0); } -#undef LCC -#undef LCCC - return entry; + silc_free(f); +} + +/* Assembles user list and users mode list from the `channel'. */ + +void silc_server_get_users_on_channel(SilcServer server, + SilcChannelEntry channel, + SilcBuffer *user_list, + SilcBuffer *mode_list, + uint32 *user_count) +{ + SilcChannelClientEntry chl; + SilcHashTableList htl; + SilcBuffer client_id_list; + SilcBuffer client_mode_list; + SilcBuffer idp; + uint32 list_count = 0, len = 0; + + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) + len += (silc_id_get_len(chl->client->id, SILC_ID_CLIENT) + 4); + + client_id_list = silc_buffer_alloc(len); + client_mode_list = + silc_buffer_alloc(4 * silc_hash_table_count(channel->user_list)); + silc_buffer_pull_tail(client_id_list, SILC_BUFFER_END(client_id_list)); + silc_buffer_pull_tail(client_mode_list, SILC_BUFFER_END(client_mode_list)); + + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + /* Client ID */ + idp = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT); + silc_buffer_put(client_id_list, idp->data, idp->len); + silc_buffer_pull(client_id_list, idp->len); + silc_buffer_free(idp); + + /* Client's mode on channel */ + SILC_PUT32_MSB(chl->mode, client_mode_list->data); + silc_buffer_pull(client_mode_list, 4); + + list_count++; + } + silc_buffer_push(client_id_list, + client_id_list->data - client_id_list->head); + silc_buffer_push(client_mode_list, + client_mode_list->data - client_mode_list->head); + + *user_list = client_id_list; + *mode_list = client_mode_list; + *user_count = list_count; } -/* Create new client. This processes incoming NEW_CLIENT packet and creates - Client ID for the client and adds it to lists and cache. */ +/* Saves users and their modes to the `channel'. */ -SilcClientList *silc_server_new_client(SilcServer server, +void silc_server_save_users_on_channel(SilcServer server, SilcSocketConnection sock, - SilcPacketContext *packet) + SilcChannelEntry channel, + SilcClientID *noadd, + SilcBuffer user_list, + SilcBuffer mode_list, + uint32 user_count) { - SilcBuffer buffer = packet->buffer; - SilcClientList *id_entry; - char *username = NULL, *realname = NULL, *id_string; - SilcBuffer reply; + int i; + + for (i = 0; i < user_count; i++) { + uint16 idp_len; + uint32 mode; + SilcClientID *client_id; + SilcClientEntry client; + + /* Client ID */ + SILC_GET16_MSB(idp_len, user_list->data + 2); + idp_len += 4; + client_id = silc_id_payload_parse_id(user_list->data, idp_len); + silc_buffer_pull(user_list, idp_len); + if (!client_id) + continue; - SILC_LOG_DEBUG(("Creating new client")); + /* Mode */ + SILC_GET32_MSB(mode, mode_list->data); + silc_buffer_pull(mode_list, 4); - if (sock->type != SILC_SOCKET_TYPE_CLIENT) - return NULL; + if (noadd && SILC_ID_CLIENT_COMPARE(client_id, noadd)) { + silc_free(client_id); + continue; + } + + /* Check if we have this client cached already. */ + client = silc_idlist_find_client_by_id(server->local_list, client_id, + server->server_type, NULL); + if (!client) + client = silc_idlist_find_client_by_id(server->global_list, + client_id, server->server_type, + NULL); + if (!client) { + /* If router did not find such Client ID in its lists then this must + be bogus client or some router in the net is buggy. */ + if (server->server_type == SILC_ROUTER) { + silc_free(client_id); + continue; + } -#define LCC(x) server->local_list->client_cache[(x) - 32] -#define LCCC(x) server->local_list->client_cache_count[(x) - 32] + /* We don't have that client anywhere, add it. The client is added + to global list since server didn't have it in the lists so it must be + global. */ + client = silc_idlist_add_client(server->global_list, NULL, NULL, NULL, + silc_id_dup(client_id, SILC_ID_CLIENT), + sock->user_data, NULL); + if (!client) { + SILC_LOG_ERROR(("Could not add new client to the ID Cache")); + silc_free(client_id); + continue; + } - silc_buffer_unformat(buffer, - SILC_STR_UI16_STRING_ALLOC(&username), - SILC_STR_UI16_STRING_ALLOC(&realname), - SILC_STR_END); + client->data.status |= SILC_IDLIST_STATUS_REGISTERED; + } - /* Set the pointers to the client list and create new client ID */ - id_entry = (SilcClientList *)sock->user_data; - id_entry->nickname = strdup(username); - id_entry->username = username; - id_entry->userinfo = realname; - silc_id_create_client_id(server->id, server->rng, server->md5hash, - username, &id_entry->id); - - /* Add to client cache */ - LCCC(username[0]) = silc_idcache_add(&LCC(username[0]), - LCCC(username[0]), - username, SILC_ID_CLIENT, - id_entry->id, (void *)id_entry); - - /* Notify our router about new client on the SILC network */ - if (!server->standalone) - silc_server_send_new_id(server, (SilcSocketConnection) - server->id_entry->router->connection, - server->server_type == SILC_SERVER ? TRUE : FALSE, - id_entry->id, SILC_ID_CLIENT, SILC_ID_CLIENT_LEN); - - /* Send the new client ID to the client. */ - id_string = silc_id_id2str(id_entry->id, SILC_ID_CLIENT); - reply = silc_buffer_alloc(2 + 2 + SILC_ID_CLIENT_LEN); - silc_buffer_pull_tail(reply, SILC_BUFFER_END(reply)); - silc_buffer_format(reply, - SILC_STR_UI_SHORT(SILC_ID_CLIENT), - SILC_STR_UI_SHORT(SILC_ID_CLIENT_LEN), - SILC_STR_UI_XNSTRING(id_string, SILC_ID_CLIENT_LEN), - SILC_STR_END); - silc_server_packet_send(server, sock, SILC_PACKET_NEW_ID, 0, - reply->data, reply->len, FALSE); - silc_free(id_string); - silc_buffer_free(reply); - - /* Send some nice info to the client */ - silc_server_send_notify(server, sock, - "Welcome to the SILC Network %s@%s", - username, - sock->hostname ? sock->hostname : sock->ip); - silc_server_send_notify(server, sock, - "Your host is %s, running version %s", - server->config->server_info->server_name, - server_version); - silc_server_send_notify(server, sock, - "Your connection is secured with %s cipher, " - "key length %d bits", - id_entry->send_key->cipher->name, - id_entry->send_key->cipher->key_len); - silc_server_send_notify(server, sock, - "Your current nickname is %s", - id_entry->nickname); - - /* XXX Send motd */ - -#undef LCC -#undef LCCC - return id_entry; + silc_free(client_id); + + if (!silc_server_client_on_channel(client, channel)) { + /* Client was not on the channel, add it. */ + SilcChannelClientEntry chl = silc_calloc(1, sizeof(*chl)); + chl->client = client; + chl->mode = mode; + chl->channel = channel; + silc_hash_table_add(channel->user_list, chl->client, chl); + silc_hash_table_add(client->channels, chl->channel, chl); + } + } } -/* Create new server. This processes incoming NEW_SERVER packet and - saves the received Server ID. The server is our locally connected - server thus we save all the information and save it to local list. - This funtion can be used by both normal server and router server. - If normal server uses this it means that its router has connected - to the server. If router uses this it means that one of the cell's - servers is connected to the router. */ +/* Lookups route to the client indicated by the `id_data'. The connection + object and internal data object is returned. Returns NULL if route + could not be found to the client. If the `client_id' is specified then + it is used and the `id_data' is ignored. */ -SilcServerList *silc_server_new_server(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet) +SilcSocketConnection silc_server_get_client_route(SilcServer server, + unsigned char *id_data, + uint32 id_len, + SilcClientID *client_id, + SilcIDListData *idata) { - SilcBuffer buffer = packet->buffer; - SilcServerList *id_entry; - unsigned char *server_name, *id_string; + SilcClientID *id; + SilcClientEntry client; - SILC_LOG_DEBUG(("Creating new server")); + SILC_LOG_DEBUG(("Start")); - if (sock->type != SILC_SOCKET_TYPE_SERVER && - sock->type != SILC_SOCKET_TYPE_ROUTER) - return NULL; + /* Decode destination Client ID */ + if (!client_id) { + id = silc_id_str2id(id_data, id_len, SILC_ID_CLIENT); + if (!id) { + SILC_LOG_ERROR(("Could not decode destination Client ID, dropped")); + return NULL; + } + } else { + id = silc_id_dup(client_id, SILC_ID_CLIENT); + } -#define LSC(x) server->local_list->server_cache[(x) - 32] -#define LSCC(x) server->local_list->server_cache_count[(x) - 32] + /* If the destination belongs to our server we don't have to route + the packet anywhere but to send it to the local destination. */ + client = silc_idlist_find_client_by_id(server->local_list, id, TRUE, NULL); + if (client) { + silc_free(id); - silc_buffer_unformat(buffer, - SILC_STR_UI16_STRING_ALLOC(&id_string), - SILC_STR_UI16_STRING_ALLOC(&server_name), - SILC_STR_END); + /* If we are router and the client has router then the client is in + our cell but not directly connected to us. */ + if (server->server_type == SILC_ROUTER && client->router) { + /* We are of course in this case the client's router thus the route + to the client is the server who owns the client. So, we will send + the packet to that server. */ + if (idata) + *idata = (SilcIDListData)client->router; + return client->router->connection; + } - /* Save ID and name */ - id_entry = (SilcServerList *)sock->user_data; - id_entry->id = silc_id_str2id(id_string, SILC_ID_SERVER); - id_entry->server_name = server_name; - - /* Add to server cache */ - LSCC(server_name[0]) = - silc_idcache_add(&LSC(server_name[0]), - LSCC(server_name[0]), - server_name, SILC_ID_SERVER, - id_entry->id, (void *)id_entry); - - /* Distribute the information about new server in the SILC network - to our router. If we are normal server we won't send anything - since this connection must be our router connection. */ - if (server->server_type == SILC_ROUTER && !server->standalone) - silc_server_send_new_id(server, server->id_entry->router->connection, - TRUE, id_entry->id, SILC_ID_SERVER, - SILC_ID_SERVER_LEN); + /* Seems that client really is directly connected to us */ + if (idata) + *idata = (SilcIDListData)client; + return client->connection; + } - silc_free(id_string); + /* Destination belongs to someone not in this server. If we are normal + server our action is to send the packet to our router. */ + if (server->server_type != SILC_ROUTER && !server->standalone) { + silc_free(id); + if (idata) + *idata = (SilcIDListData)server->router; + return server->router->connection; + } + + /* We are router and we will perform route lookup for the destination + and send the packet to fastest route. */ + if (server->server_type == SILC_ROUTER && !server->standalone) { + /* Check first that the ID is valid */ + client = silc_idlist_find_client_by_id(server->global_list, id, + TRUE, NULL); + if (client) { + SilcSocketConnection dst_sock; + + dst_sock = silc_server_route_get(server, id, SILC_ID_CLIENT); + + silc_free(id); + if (idata) + *idata = (SilcIDListData)dst_sock->user_data; + return dst_sock; + } + } -#undef LSC -#undef LSCC - return id_entry; + silc_free(id); + return NULL; } -/* Processes incoming New ID Payload. New ID Payload is used to distribute - information about newly registered clients, servers and created - channels. */ +/* Encodes and returns channel list of channels the `client' has joined. + Secret channels are not put to the list. */ -void silc_server_new_id(SilcServer server, SilcSocketConnection sock, - SilcPacketContext *packet) +SilcBuffer silc_server_get_client_channel_list(SilcServer server, + SilcClientEntry client) { - SilcBuffer buffer = packet->buffer; - SilcIdType id_type; - unsigned char *id_string; - void *id; + SilcBuffer buffer = NULL; + SilcChannelEntry channel; + SilcChannelClientEntry chl; + SilcHashTableList htl; + unsigned char *cid; + uint32 id_len; + uint16 name_len; + int len; + + silc_hash_table_list(client->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + channel = chl->channel; + + if (channel->mode & SILC_CHANNEL_MODE_SECRET || + channel->mode & SILC_CHANNEL_MODE_PRIVATE) + continue; - SILC_LOG_DEBUG(("Processing new ID")); + cid = silc_id_id2str(channel->id, SILC_ID_CHANNEL); + id_len = silc_id_get_len(channel->id, SILC_ID_CHANNEL); + name_len = strlen(channel->channel_name); + + len = 4 + name_len + id_len + 4; + buffer = silc_buffer_realloc(buffer, + (buffer ? (buffer)->truelen + len : len)); + silc_buffer_pull_tail(buffer, ((buffer)->end - (buffer)->data)); + silc_buffer_format(buffer, + SILC_STR_UI_SHORT(name_len), + SILC_STR_UI_XNSTRING(channel->channel_name, + name_len), + SILC_STR_UI_SHORT(id_len), + SILC_STR_UI_XNSTRING(cid, id_len), + SILC_STR_UI_INT(chl->mode), /* Client's mode */ + SILC_STR_END); + silc_buffer_pull(buffer, len); + silc_free(cid); + } - if (sock->type == SILC_SOCKET_TYPE_CLIENT || - server->server_type == SILC_SERVER) - return; + if (buffer) + silc_buffer_push(buffer, buffer->data - buffer->head); - silc_buffer_unformat(buffer, - SILC_STR_UI_SHORT(&id_type), - SILC_STR_UI16_STRING_ALLOC(&id_string), - SILC_STR_END); + return buffer; +} - /* Normal server cannot have other normal server connections */ - if (id_type == SILC_ID_SERVER && sock->type == SILC_SOCKET_TYPE_SERVER) - goto out; +/* Finds client entry by Client ID and if it is not found then resolves + it using WHOIS command. */ - id = silc_id_str2id(id_string, id_type); - if (!id) - goto out; +SilcClientEntry silc_server_get_client_resolve(SilcServer server, + SilcClientID *client_id) +{ + SilcClientEntry client; - /* XXX Do check whether the packet is coming outside the cell or - from someone inside the cell. If outside use global lists otherwise - local lists. */ - /* XXX If using local list set the idlist->connection to the sender's - socket connection as it is used in packet sending */ + client = silc_idlist_find_client_by_id(server->local_list, client_id, + TRUE, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->global_list, + client_id, TRUE, NULL); + if (!client && server->server_type == SILC_ROUTER) + return NULL; + } - switch(id_type) { - case SILC_ID_CLIENT: - { - SilcClientList *idlist; - - /* Add the client to our local list. We are router and we keep - cell specific local database of all clients in the cell. */ - silc_idlist_add_client(&server->local_list->clients, NULL, NULL, NULL, - id, sock->user_data, NULL, NULL, - NULL, NULL, &idlist); - idlist->connection = sock; - } - break; + if (!client && server->standalone) + return NULL; - case SILC_ID_SERVER: - { - SilcServerList *idlist; - - /* Add the server to our local list. We are router and we keep - cell specific local database of all servers in the cell. */ - silc_idlist_add_server(&server->local_list->servers, NULL, 0, - id, server->id_entry, NULL, NULL, - NULL, NULL, &idlist); - idlist->connection = sock; - } - break; + if (!client || !client->nickname || !client->username) { + SilcBuffer buffer, idp; + + client->data.status |= SILC_IDLIST_STATUS_RESOLVING; + client->data.status &= ~SILC_IDLIST_STATUS_RESOLVED; + client->resolve_cmd_ident = ++server->cmd_ident; + + idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT); + buffer = silc_command_payload_encode_va(SILC_COMMAND_WHOIS, + server->cmd_ident, 1, + 3, idp->data, idp->len); + silc_server_packet_send(server, client ? client->router->connection : + server->router->connection, + SILC_PACKET_COMMAND, 0, + buffer->data, buffer->len, FALSE); + silc_buffer_free(idp); + silc_buffer_free(buffer); + return NULL; + } - case SILC_ID_CHANNEL: - /* Add the channel to our local list. We are router and we keep - cell specific local database of all channels in the cell. */ - silc_idlist_add_channel(&server->local_list->channels, NULL, 0, - id, server->id_entry, NULL, NULL); - break; + return client; +} - default: - goto out; - break; +/* A timeout callback for the re-key. We will be the initiator of the + re-key protocol. */ + +SILC_TASK_CALLBACK(silc_server_rekey_callback) +{ + SilcSocketConnection sock = (SilcSocketConnection)context; + SilcIDListData idata = (SilcIDListData)sock->user_data; + SilcServer server = (SilcServer)idata->rekey->context; + SilcProtocol protocol; + SilcServerRekeyInternalContext *proto_ctx; + + SILC_LOG_DEBUG(("Start")); + + /* Allocate internal protocol context. This is sent as context + to the protocol. */ + proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); + proto_ctx->server = (void *)server; + proto_ctx->sock = sock; + proto_ctx->responder = FALSE; + proto_ctx->pfs = idata->rekey->pfs; + + /* Perform rekey protocol. Will call the final callback after the + protocol is over. */ + silc_protocol_alloc(SILC_PROTOCOL_SERVER_REKEY, + &protocol, proto_ctx, silc_server_rekey_final); + sock->protocol = protocol; + + /* Run the protocol */ + silc_protocol_execute(protocol, server->schedule, 0, 0); + + /* Re-register re-key timeout */ + silc_schedule_task_add(server->schedule, sock->sock, + silc_server_rekey_callback, + context, idata->rekey->timeout, 0, + SILC_TASK_TIMEOUT, SILC_TASK_PRI_NORMAL); +} + +/* The final callback for the REKEY protocol. This will actually take the + new key material into use. */ + +SILC_TASK_CALLBACK_GLOBAL(silc_server_rekey_final) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerRekeyInternalContext *ctx = + (SilcServerRekeyInternalContext *)protocol->context; + SilcServer server = (SilcServer)ctx->server; + SilcSocketConnection sock = ctx->sock; + + SILC_LOG_DEBUG(("Start")); + + if (protocol->state == SILC_PROTOCOL_STATE_ERROR || + protocol->state == SILC_PROTOCOL_STATE_FAILURE) { + /* Error occured during protocol */ + SILC_LOG_ERROR(("Error occurred during rekey protocol")); + silc_protocol_cancel(protocol, server->schedule); + silc_protocol_free(protocol); + sock->protocol = NULL; + if (ctx->packet) + silc_packet_context_free(ctx->packet); + if (ctx->ske) + silc_ske_free(ctx->ske); + silc_free(ctx); + return; } - out: - silc_free(id_string); + /* Purge the outgoing data queue to assure that all rekey packets really + go to the network before we quit the protocol. */ + silc_server_packet_queue_purge(server, sock); + + /* Cleanup */ + silc_protocol_free(protocol); + sock->protocol = NULL; + if (ctx->packet) + silc_packet_context_free(ctx->packet); + if (ctx->ske) + silc_ske_free(ctx->ske); + silc_free(ctx); } diff --git a/apps/silcd/server.h b/apps/silcd/server.h index ebe661c9..1020a81a 100644 --- a/apps/silcd/server.h +++ b/apps/silcd/server.h @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -24,107 +24,200 @@ /* Forward declaration for SILC Server object. The actual object is defined in internal header file for server routines. I want to keep the object private hence this declaration. */ -typedef struct SilcServerObjectStruct *SilcServer; +typedef struct SilcServerStruct *SilcServer; -#define SILC_SERVER_MAX_CONNECTIONS 10000 +/* Forward declaration of backup server context */ +typedef struct SilcServerBackupStruct *SilcServerBackup; + +#define SILC_SERVER_MAX_CONNECTIONS 1000 /* General definitions */ +/* SILC port */ +#define SILC_PORT 768; + +/* Server and router. Used internally by the code. */ #define SILC_SERVER 0 #define SILC_ROUTER 1 +#define SILC_BACKUP_ROUTER 2 + +/* Connection retry timeout. We implement exponential backoff algorithm + in connection retry. The interval of timeuot grows when retry count + grows. */ +#define SILC_SERVER_RETRY_COUNT 4 /* Max retry count */ +#define SILC_SERVER_RETRY_MULTIPLIER 7 / 4 /* Interval growth */ +#define SILC_SERVER_RETRY_RANDOMIZER 2 /* timeout += rnd % 2 */ +#define SILC_SERVER_RETRY_INTERVAL_MIN 10 /* Min retry timeout */ +#define SILC_SERVER_RETRY_INTERVAL_MAX 600 /* Max generated timeout */ + +/* + Silc Server Params. + + Structure to hold various default parameters for server that can be + given before running the server. + +*/ +typedef struct { + uint32 retry_count; + uint32 retry_interval_min; + uint32 retry_interval_min_usec; + uint32 retry_interval_max; + char retry_keep_trying; + + uint32 protocol_timeout; + uint32 protocol_timeout_usec; + + char require_reverse_mapping; +} *SilcServerParams; + +/* Callback function that is called after the key exchange and connection + authentication protocols has been completed with a remote router. The + `server_entry' is the remote router entry. */ +typedef void (*SilcServerConnectRouterCallback)(SilcServer server, + SilcServerEntry server_entry, + void *context); + +typedef struct { + SilcSocketConnection sock; + + /* Remote host name and port */ + char *remote_host; + int remote_port; + bool backup; + char *backup_replace_ip; + int backup_replace_port; + + /* Current connection retry info */ + uint32 retry_count; + uint32 retry_timeout; + + /* Back pointer to server */ + SilcServer server; + + SilcServerConnectRouterCallback callback; + void *callback_context; +} *SilcServerConnection; + +/* Macros */ + +/* This macro is used to send notify messages with formatted string. The + string is formatted with arguments and the formatted string is sent as + argument. */ +#define SILC_SERVER_SEND_NOTIFY(server, sock, type, fmt) \ +do { \ + char *__fmt__ = silc_format fmt; \ + silc_server_send_notify(server, sock, FALSE, \ + type, 1, __fmt__, strlen(__fmt__)); \ + silc_free(__fmt__); \ +} while(0); + +/* Check whether rekey protocol is active */ +#define SILC_SERVER_IS_REKEY(sock) \ + (sock->protocol && sock->protocol->protocol && \ + sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_REKEY) /* Prototypes */ int silc_server_alloc(SilcServer *new_server); void silc_server_free(SilcServer server); int silc_server_init(SilcServer server); +void silc_server_daemonise(SilcServer server); void silc_server_run(SilcServer server); void silc_server_stop(SilcServer server); -void silc_server_packet_send(SilcServer server, - SilcSocketConnection sock, - SilcPacketType type, - SilcPacketFlags flags, - unsigned char *data, - unsigned int data_len, - int force_send); -void silc_server_packet_send_dest(SilcServer server, - SilcSocketConnection sock, - SilcPacketType type, - SilcPacketFlags flags, - void *dst_id, - SilcIdType dst_id_type, - unsigned char *data, - unsigned int data_len, - int force_send); -void silc_server_packet_forward(SilcServer server, - SilcSocketConnection sock, - unsigned char *data, unsigned int data_len, - int force_send); -void silc_server_packet_send_to_channel(SilcServer server, - SilcChannelList *channel, - unsigned char *data, - unsigned int data_len, - int force_send); -void silc_server_packet_relay_to_channel(SilcServer server, - SilcSocketConnection sender_sock, - SilcChannelList *channel, - void *sender, - SilcIdType sender_type, - unsigned char *data, - unsigned int data_len, - int force_send); -void silc_server_packet_relay_command_reply(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet); +void silc_server_start_key_exchange(SilcServer server, + SilcServerConnection sconn, + int sock); +bool silc_server_packet_parse(SilcPacketParserContext *parser_context, + void *context); +void silc_server_packet_parse_type(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); +void silc_server_create_connection(SilcServer server, + char *remote_host, uint32 port); void silc_server_close_connection(SilcServer server, SilcSocketConnection sock); +void silc_server_free_client_data(SilcServer server, + SilcSocketConnection sock, + SilcClientEntry client, + int notify, + char *signoff); void silc_server_free_sock_user_data(SilcServer server, SilcSocketConnection sock); void silc_server_remove_from_channels(SilcServer server, SilcSocketConnection sock, - SilcClientList *client); + SilcClientEntry client, + int notify, + char *signoff_message, + int keygen); +int silc_server_remove_from_one_channel(SilcServer server, + SilcSocketConnection sock, + SilcChannelEntry channel, + SilcClientEntry client, + int notify); void silc_server_disconnect_remote(SilcServer server, SilcSocketConnection sock, const char *fmt, ...); -void silc_server_private_message(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet); -void silc_server_channel_message(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet); -void silc_server_channel_key(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet); -void silc_server_send_error(SilcServer server, - SilcSocketConnection sock, - const char *fmt, ...); -void silc_server_send_notify(SilcServer server, - SilcSocketConnection sock, - const char *fmt, ...); -void silc_server_send_notify_to_channel(SilcServer server, - SilcChannelList *channel, - const char *fmt, ...); -void silc_server_send_new_id(SilcServer server, - SilcSocketConnection sock, - int broadcast, - void *id, SilcIdType id_type, - unsigned int id_len); -void silc_server_send_replace_id(SilcServer server, - SilcSocketConnection sock, - int broadcast, - void *old_id, SilcIdType old_id_type, - unsigned int old_id_len, - void *new_id, SilcIdType new_id_type, - unsigned int new_id_len); -SilcChannelList *silc_server_new_channel(SilcServer server, - SilcServerID *router_id, - char *cipher, char *channel_name); -SilcClientList *silc_server_new_client(SilcServer server, - SilcSocketConnection sock, - SilcPacketContext *packet); -SilcServerList *silc_server_new_server(SilcServer server, +SilcChannelEntry silc_server_create_new_channel(SilcServer server, + SilcServerID *router_id, + char *cipher, + char *hmac, + char *channel_name, + int broadcast); +SilcChannelEntry +silc_server_create_new_channel_with_id(SilcServer server, + char *cipher, + char *hmac, + char *channel_name, + SilcChannelID *channel_id, + int broadcast); +bool silc_server_create_channel_key(SilcServer server, + SilcChannelEntry channel, + uint32 key_len); +SilcChannelEntry silc_server_save_channel_key(SilcServer server, + SilcBuffer key_payload, + SilcChannelEntry channel); +void silc_server_perform_heartbeat(SilcSocketConnection sock, + void *hb_context); +void silc_server_announce_get_channel_users(SilcServer server, + SilcChannelEntry channel, + SilcBuffer *channel_users, + SilcBuffer *channel_users_modes); +void silc_server_announce_get_channels(SilcServer server, + SilcIDList id_list, + SilcBuffer *channels, + SilcBuffer *channel_users, + SilcBuffer **channel_users_modes, + uint32 *channel_users_modes_c, + SilcChannelID ***channel_ids, + unsigned long creation_time); +void silc_server_announce_servers(SilcServer server, bool global, + unsigned long creation_time, + SilcSocketConnection remote); +void silc_server_announce_clients(SilcServer server, + unsigned long creation_time, + SilcSocketConnection remote); +void silc_server_announce_channels(SilcServer server, + unsigned long creation_time, + SilcSocketConnection remote); +void silc_server_get_users_on_channel(SilcServer server, + SilcChannelEntry channel, + SilcBuffer *user_list, + SilcBuffer *mode_list, + uint32 *user_count); +void silc_server_save_users_on_channel(SilcServer server, SilcSocketConnection sock, - SilcPacketContext *packet); -void silc_server_new_id(SilcServer server, SilcSocketConnection sock, - SilcPacketContext *packet); + SilcChannelEntry channel, + SilcClientID *noadd, + SilcBuffer user_list, + SilcBuffer mode_list, + uint32 user_count); +SilcSocketConnection silc_server_get_client_route(SilcServer server, + unsigned char *id_data, + uint32 id_len, + SilcClientID *client_id, + SilcIDListData *idata); +SilcBuffer silc_server_get_client_channel_list(SilcServer server, + SilcClientEntry client); +SilcClientEntry silc_server_get_client_resolve(SilcServer server, + SilcClientID *client_id); #endif diff --git a/apps/silcd/server_backup.c b/apps/silcd/server_backup.c new file mode 100644 index 00000000..423710f9 --- /dev/null +++ b/apps/silcd/server_backup.c @@ -0,0 +1,1235 @@ +/* + + server_backup.c + + Author: Pekka Riikonen + + Copyright (C) 2001 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; version 2 of the License. + + 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 "serverincludes.h" +#include "server_internal.h" + +SILC_TASK_CALLBACK(silc_server_protocol_backup_done); + +/* Backup router */ +typedef struct { + SilcServerEntry server; + SilcIDIP ip; + uint16 port; + bool local; +} SilcServerBackupEntry; + +/* Holds IP address and port of the primary router that was replaced + by backup router. */ +typedef struct { + SilcIDIP ip; + uint16 port; + SilcServerEntry server; /* Backup router that replaced the primary */ +} SilcServerBackupReplaced; + +/* Backup context */ +struct SilcServerBackupStruct { + SilcServerBackupEntry *servers; + uint32 servers_count; + SilcServerBackupReplaced **replaced; + uint32 replaced_count; +}; + +typedef struct { + uint8 session; + bool connected; + SilcServerEntry server_entry; +} SilcServerBackupProtocolSession; + +/* Backup resuming protocol context */ +typedef struct { + SilcServer server; + SilcSocketConnection sock; + bool responder; + uint8 type; + uint8 session; + SilcServerBackupProtocolSession *sessions; + uint32 sessions_count; + long start; +} *SilcServerBackupProtocolContext; + +/* Adds the `backup_server' to be one of our backup router. This can be + called multiple times to set multiple backup routers. The `ip' and `port' + is the IP and port that the `backup_router' will replace if the `ip' + will become unresponsive. If `local' is TRUE then the `backup_server' is + in the local cell, if FALSE it is in some other cell. */ + +void silc_server_backup_add(SilcServer server, SilcServerEntry backup_server, + const char *ip, int port, bool local) +{ + int i; + + SILC_LOG_DEBUG(("Start")); + + if (!ip) + return; + + if (!server->backup) + server->backup = silc_calloc(1, sizeof(*server->backup)); + + for (i = 0; i < server->backup->servers_count; i++) { + if (!server->backup->servers[i].server) { + server->backup->servers[i].server = backup_server; + server->backup->servers[i].local = local; + memset(server->backup->servers[i].ip.data, 0, + sizeof(server->backup->servers[i].ip.data)); + silc_net_addr2bin_ne(ip, server->backup->servers[i].ip.data, + sizeof(server->backup->servers[i].ip.data)); + //server->backup->servers[i].port = port; + return; + } + } + + i = server->backup->servers_count; + server->backup->servers = silc_realloc(server->backup->servers, + sizeof(*server->backup->servers) * + (i + 1)); + server->backup->servers[i].server = backup_server; + server->backup->servers[i].local = local; + memset(server->backup->servers[i].ip.data, 0, + sizeof(server->backup->servers[i].ip.data)); + silc_net_addr2bin_ne(ip, server->backup->servers[i].ip.data, + sizeof(server->backup->servers[i].ip.data)); + //server->backup->servers[i].port = server_id->port; + server->backup->servers_count++; +} + +/* Returns backup router for IP and port in `replacing' or NULL if there + does not exist backup router. */ + +SilcServerEntry silc_server_backup_get(SilcServer server, + SilcServerID *server_id) +{ + int i; + + SILC_LOG_DEBUG(("Start")); + + if (!server->backup) + return NULL; + + for (i = 0; i < server->backup->servers_count; i++) { + SILC_LOG_HEXDUMP(("IP"), server_id->ip.data, 16); + SILC_LOG_HEXDUMP(("IP"), server->backup->servers[i].ip.data, 16); + if (server->backup->servers[i].server && + !memcmp(&server->backup->servers[i].ip, &server_id->ip.data, + sizeof(server_id->ip.data))) + return server->backup->servers[i].server; + } + + return NULL; +} + +/* Deletes the backup server `server_entry'. */ +void silc_server_backup_del(SilcServer server, SilcServerEntry server_entry) +{ + int i; + + SILC_LOG_DEBUG(("Start")); + + if (!server->backup) + return ; + + for (i = 0; i < server->backup->servers_count; i++) { + if (server->backup->servers[i].server == server_entry) { + server->backup->servers[i].server = NULL; + return; + } + } +} + +/* Marks the IP address and port from the `server_id' as being replaced + by backup router indicated by the `server'. If the router connects at + a later time we can check whether it has been replaced by an backup + router. */ + +void silc_server_backup_replaced_add(SilcServer server, + SilcServerID *server_id, + SilcServerEntry server_entry) +{ + int i; + SilcServerBackupReplaced *r = silc_calloc(1, sizeof(*r));; + + SILC_LOG_DEBUG(("Start")); + + if (!server->backup) + server->backup = silc_calloc(1, sizeof(*server->backup)); + if (!server->backup->replaced) { + server->backup->replaced = + silc_calloc(1, sizeof(*server->backup->replaced)); + server->backup->replaced_count = 1; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Replaced added")); + + memcpy(&r->ip, &server_id->ip, sizeof(server_id->ip)); + //r->port = server_id->port; + r->server = server_entry; + + for (i = 0; i < server->backup->replaced_count; i++) { + if (!server->backup->replaced[i]) { + server->backup->replaced[i] = r; + return; + } + } + + i = server->backup->replaced_count; + server->backup->replaced = silc_realloc(server->backup->replaced, + sizeof(*server->backup->replaced) * + (i + 1)); + server->backup->replaced[i] = r; + server->backup->replaced_count++; +} + +/* Checks whether the IP address and port from the `server_id' has been + replaced by an backup router. If it has been then this returns TRUE + and the bacup router entry to the `server' pointer if non-NULL. Returns + FALSE if the router is not replaced by backup router. */ + +bool silc_server_backup_replaced_get(SilcServer server, + SilcServerID *server_id, + SilcServerEntry *server_entry) +{ + int i; + + SILC_LOG_DEBUG(("Start")); + + SILC_LOG_DEBUG(("*************************************")); + + if (!server->backup || !server->backup->replaced) + return FALSE; + + for (i = 0; i < server->backup->replaced_count; i++) { + if (!server->backup->replaced[i]) + continue; + SILC_LOG_HEXDUMP(("IP"), server_id->ip.data, server_id->ip.data_len); + SILC_LOG_HEXDUMP(("IP"), server->backup->replaced[i]->ip.data, + server->backup->replaced[i]->ip.data_len); + if (!memcmp(&server->backup->replaced[i]->ip, &server_id->ip.data, + sizeof(server_id->ip.data))) { + if (server_entry) + *server_entry = server->backup->replaced[i]->server; + SILC_LOG_DEBUG(("REPLACED")); + return TRUE; + } + } + + SILC_LOG_DEBUG(("NOT REPLACED")); + return FALSE; +} + +/* Deletes a replaced host by the set `server_entry. */ + +void silc_server_backup_replaced_del(SilcServer server, + SilcServerEntry server_entry) +{ + int i; + + SILC_LOG_DEBUG(("Start")); + + if (!server->backup || !server->backup->replaced) + return; + + for (i = 0; i < server->backup->replaced_count; i++) { + if (!server->backup->replaced[i]) + continue; + if (server->backup->replaced[i]->server == server_entry) { + silc_free(server->backup->replaced[i]); + server->backup->replaced[i] = NULL; + return; + } + } +} + +/* Broadcast the received packet indicated by `packet' to all of our backup + routers. All router wide information is passed using broadcast packets. + That is why all backup routers need to get this data too. It is expected + that the caller already knows that the `packet' is broadcast packet. */ + +void silc_server_backup_broadcast(SilcServer server, + SilcSocketConnection sender, + SilcPacketContext *packet) +{ + SilcServerEntry backup; + SilcSocketConnection sock; + SilcBuffer buffer; + SilcIDListData idata; + int i; + + if (!server->backup || server->server_type != SILC_ROUTER) + return; + + SILC_LOG_DEBUG(("Broadcasting received packet to backup routers")); + + buffer = packet->buffer; + silc_buffer_push(buffer, buffer->data - buffer->head); + + for (i = 0; i < server->backup->servers_count; i++) { + backup = server->backup->servers[i].server; + + if (!backup || backup->connection == sender || + server->backup->servers[i].local == FALSE) + continue; + + idata = (SilcIDListData)backup; + sock = backup->connection; + + silc_packet_send_prepare(sock, 0, 0, buffer->len); + silc_buffer_put(sock->outbuf, buffer->data, buffer->len); + silc_packet_encrypt(idata->send_key, idata->hmac_send, idata->psn_send++, + sock->outbuf, sock->outbuf->len); + + SILC_LOG_HEXDUMP(("Broadcasted packet, len %d", sock->outbuf->len), + sock->outbuf->data, sock->outbuf->len); + + /* Now actually send the packet */ + silc_server_packet_send_real(server, sock, FALSE); + } +} + +/* A generic routine to send data to all backup routers. If the `sender' + is provided it will indicate the original sender of the packet and the + packet won't be resent to that entity. The `data' is the data that will + be assembled to packet context before sending. The packet will be + encrypted this function. If the `force_send' is TRUE the data is sent + immediately and not put to queue. If `local' is TRUE then the packet + will be sent only to local backup routers inside the cell. If false the + packet can go from one cell to the other. This function has no effect + if there are no any backup routers. */ + +void silc_server_backup_send(SilcServer server, + SilcServerEntry sender, + SilcPacketType type, + SilcPacketFlags flags, + unsigned char *data, + uint32 data_len, + bool force_send, + bool local) +{ + SilcServerEntry backup; + SilcSocketConnection sock; + int i; + + if (!server->backup || server->server_type != SILC_ROUTER) + return; + + SILC_LOG_DEBUG(("Start")); + + for (i = 0; i < server->backup->servers_count; i++) { + backup = server->backup->servers[i].server; + if (!backup) + continue; + + if (sender == backup) + continue; + + if (local && server->backup->servers[i].local == FALSE) + continue; + + sock = backup->connection; + silc_server_packet_send(server, backup->connection, type, flags, + data, data_len, force_send); + } +} + +/* Same as silc_server_backup_send but sets a specific Destination ID to + the packet. The Destination ID is indicated by the `dst_id' and the + ID type `dst_id_type'. For example, packets destined to channels must + be sent using this function. */ + +void silc_server_backup_send_dest(SilcServer server, + SilcServerEntry sender, + SilcPacketType type, + SilcPacketFlags flags, + void *dst_id, + SilcIdType dst_id_type, + unsigned char *data, + uint32 data_len, + bool force_send, + bool local) +{ + SilcServerEntry backup; + SilcSocketConnection sock; + int i; + + if (!server->backup || server->server_type != SILC_ROUTER) + return; + + SILC_LOG_DEBUG(("Start")); + + for (i = 0; i < server->backup->servers_count; i++) { + backup = server->backup->servers[i].server; + if (!backup) + continue; + + if (sender == backup) + continue; + + if (local && server->backup->servers[i].local == FALSE) + continue; + + sock = backup->connection; + silc_server_packet_send_dest(server, backup->connection, type, flags, + dst_id, dst_id_type, data, data_len, + force_send); + } +} + +/* Processes incoming RESUME_ROUTER packet. This can give the packet + for processing to the protocol handler or allocate new protocol if + start command is received. */ + +void silc_server_backup_resume_router(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet) +{ + uint8 type, session; + SilcServerBackupProtocolContext ctx; + int i, ret; + + if (sock->type == SILC_SOCKET_TYPE_CLIENT || + sock->type == SILC_SOCKET_TYPE_UNKNOWN) + return; + + SILC_LOG_DEBUG(("Start")); + + ret = silc_buffer_unformat(packet->buffer, + SILC_STR_UI_CHAR(&type), + SILC_STR_UI_CHAR(&session), + SILC_STR_END); + if (ret < 0) + return; + + /* Activate the protocol for this socket if necessary */ + if ((type == SILC_SERVER_BACKUP_RESUMED || + type == SILC_SERVER_BACKUP_RESUMED_GLOBAL) && + sock->type == SILC_SOCKET_TYPE_ROUTER && !sock->protocol && + ((SilcIDListData)sock->user_data)->status & + SILC_IDLIST_STATUS_DISABLED) { + SilcServerEntry backup_router; + + if (silc_server_backup_replaced_get(server, + ((SilcServerEntry)sock-> + user_data)->id, + &backup_router)) { + SilcSocketConnection bsock = + (SilcSocketConnection)backup_router->connection; + if (bsock->protocol && bsock->protocol->protocol && + bsock->protocol->protocol->type == SILC_PROTOCOL_SERVER_BACKUP) { + sock->protocol = bsock->protocol; + ctx = sock->protocol->context; + ctx->sock = sock; + } + } + } + + /* If the backup resuming protocol is active then process the packet + in the protocol. */ + if (sock->protocol && sock->protocol->protocol && + sock->protocol->protocol->type == SILC_PROTOCOL_SERVER_BACKUP) { + ctx = sock->protocol->context; + ctx->type = type; + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Continuing protocol, type %d", type)); + + if (type != SILC_SERVER_BACKUP_RESUMED && + type != SILC_SERVER_BACKUP_RESUMED_GLOBAL) { + for (i = 0; i < ctx->sessions_count; i++) { + if (session == ctx->sessions[i].session) { + ctx->session = session; + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); + return; + } + } + } else { + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); + return; + } + + SILC_LOG_DEBUG(("Bad resume router packet")); + return; + } + + /* We don't have protocol active. If we are router and the packet is + coming from our primary router then lets check whether it means we've + been replaced by an backup router in my cell. This is usually received + immediately after we've connected to our primary router. */ + + if (sock->type == SILC_SOCKET_TYPE_ROUTER && + server->router == sock->user_data && + type == SILC_SERVER_BACKUP_REPLACED) { + /* We have been replaced by an backup router in our cell. We must + mark our primary router connection disabled since we are not allowed + to use it at this moment. */ + SilcIDListData idata = (SilcIDListData)sock->user_data; + + SILC_LOG_INFO(("We are replaced by an backup router in this cell, will " + "wait until backup resuming protocol is executed")); + + SILC_LOG_DEBUG(("We are replaced by an backup router in this cell")); + idata->status |= SILC_IDLIST_STATUS_DISABLED; + return; + } + + if (type == SILC_SERVER_BACKUP_START || + type == SILC_SERVER_BACKUP_START_GLOBAL) { + /* We have received a start for resuming protocol. */ + SilcServerBackupProtocolContext proto_ctx; + + proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); + proto_ctx->server = server; + proto_ctx->sock = sock; + proto_ctx->responder = TRUE; + proto_ctx->type = type; + proto_ctx->session = session; + proto_ctx->start = time(0); + + SILC_LOG_DEBUG(("Starting backup resuming protocol as responder")); + + /* Run the backup resuming protocol */ + silc_protocol_alloc(SILC_PROTOCOL_SERVER_BACKUP, + &sock->protocol, proto_ctx, + silc_server_protocol_backup_done); + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); + } +} + +/* Timeout task callback to connect to remote router */ + +SILC_TASK_CALLBACK(silc_server_backup_connect_to_router) +{ + SilcServerConnection sconn = (SilcServerConnection)context; + SilcServer server = sconn->server; + int sock; + + SILC_LOG_DEBUG(("Connecting to router %s:%d", sconn->remote_host, + sconn->remote_port)); + + /* Connect to remote host */ + sock = silc_net_create_connection(server->config->listen_port->local_ip, + sconn->remote_port, + sconn->remote_host); + if (sock < 0) { + silc_schedule_task_add(server->schedule, 0, + silc_server_backup_connect_to_router, + context, 5, 0, SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); + return; + } + + /* Continue with key exchange protocol */ + silc_server_start_key_exchange(server, sconn, sock); +} + +/* Constantly tries to reconnect to a primary router indicated by the + `ip' and `port'. The `connected' callback will be called when the + connection is created. */ + +void silc_server_backup_reconnect(SilcServer server, + const char *ip, uint16 port, + SilcServerConnectRouterCallback callback, + void *context) +{ + SilcServerConnection sconn; + + sconn = silc_calloc(1, sizeof(*sconn)); + sconn->server = server; + sconn->remote_host = strdup(ip); + sconn->remote_port = port; + sconn->callback = callback; + sconn->callback_context = context; + silc_schedule_task_add(server->schedule, 0, + silc_server_backup_connect_to_router, + sconn, 1, 0, SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); +} + +SILC_TASK_CALLBACK(silc_server_backup_connected_later) +{ + SilcServerBackupProtocolContext proto_ctx = + (SilcServerBackupProtocolContext)context; + SilcServer server = proto_ctx->server; + SilcSocketConnection sock = proto_ctx->sock; + + SILC_LOG_DEBUG(("Starting backup resuming protocol as initiator")); + + /* Run the backup resuming protocol */ + silc_protocol_alloc(SILC_PROTOCOL_SERVER_BACKUP, + &sock->protocol, proto_ctx, + silc_server_protocol_backup_done); + silc_protocol_execute(sock->protocol, server->schedule, 0, 0); +} + +/* Called when we've established connection back to our primary router + when we've acting as backup router and have replaced the primary router + in the cell. This function will start the backup resuming protocol. */ + +void silc_server_backup_connected(SilcServer server, + SilcServerEntry server_entry, + void *context) +{ + SilcServerBackupProtocolContext proto_ctx; + SilcSocketConnection sock = (SilcSocketConnection)server_entry->connection; + + proto_ctx = silc_calloc(1, sizeof(*proto_ctx)); + proto_ctx->server = server; + proto_ctx->sock = sock; + proto_ctx->responder = FALSE; + proto_ctx->type = SILC_SERVER_BACKUP_START; + proto_ctx->start = time(0); + + /* Start through scheduler */ + silc_schedule_task_add(server->schedule, 0, + silc_server_backup_connected_later, + proto_ctx, 0, 1, + SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); +} + +/* Called when normal server has connected to its primary router after + backup router has sent the START packet in reusming protocol. We will + move the protocol context from the backup router connection to the + primary router. */ + +static void silc_server_backup_connect_primary(SilcServer server, + SilcServerEntry server_entry, + void *context) +{ + SilcSocketConnection backup_router = (SilcSocketConnection)context; + SilcSocketConnection sock = (SilcSocketConnection)server_entry->connection; + SilcIDListData idata = (SilcIDListData)server_entry; + SilcServerBackupProtocolContext ctx = + (SilcServerBackupProtocolContext)backup_router->protocol->context; + SilcBuffer buffer; + + SILC_LOG_DEBUG(("Start")); + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Sending CONNECTED packet, session %d", ctx->session)); + + /* Send the CONNECTED packet back to the backup router. */ + buffer = silc_buffer_alloc(2); + silc_buffer_pull_tail(buffer, SILC_BUFFER_END(buffer)); + silc_buffer_format(buffer, + SILC_STR_UI_CHAR(SILC_SERVER_BACKUP_CONNECTED), + SILC_STR_UI_CHAR(ctx->session), + SILC_STR_END); + silc_server_packet_send(server, backup_router, + SILC_PACKET_RESUME_ROUTER, 0, + buffer->data, buffer->len, FALSE); + silc_buffer_free(buffer); + + /* The primary connection is disabled until it sends the RESUMED packet + to us. */ + idata->status |= SILC_IDLIST_STATUS_DISABLED; + + /* Move this protocol context from this backup router connection to + the primary router connection since it will send the subsequent + packets in this protocol. We don't talk with backup router + anymore. */ + sock->protocol = backup_router->protocol; + ctx->sock = (SilcSocketConnection)server_entry->connection; + backup_router->protocol = NULL; +} + +SILC_TASK_CALLBACK(silc_server_backup_send_resumed) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerBackupProtocolContext ctx = protocol->context; + SilcServer server = ctx->server; + SilcBuffer packet; + int i; + + for (i = 0; i < ctx->sessions_count; i++) + if (ctx->sessions[i].server_entry == ctx->sock->user_data) + ctx->session = ctx->sessions[i].session; + + /* We've received all the CONNECTED packets and now we'll send the + ENDING packet to the new primary router. */ + packet = silc_buffer_alloc(2); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + silc_buffer_format(packet, + SILC_STR_UI_CHAR(SILC_SERVER_BACKUP_ENDING), + SILC_STR_UI_CHAR(ctx->session), + SILC_STR_END); + silc_server_packet_send(server, ctx->sock, + SILC_PACKET_RESUME_ROUTER, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + + protocol->state = SILC_PROTOCOL_STATE_END; +} + +/* Resume protocol with RESUME_ROUTER packet: + + SILC_PACKET_RESUME_ROUTER: + + + + = the protocol opcode + = Identifier for this packet and any subsequent reply + packets must include this identifier. + + Types: + + 1 = To router: Comensing backup resuming protocol. This will + indicate that the sender is backup router acting as primary + and the receiver is primary router that has been replaced by + the backup router. + + To server. Comensing backup resuming protocol. This will + indicate that the sender is backup router and the receiver + must reconnect to the real primary router of the cell. + + 2 = To Router: Comesning backup resuming protocol in another + cell. The receiver will connect to its primary router + (the router that is now online again) but will not use + the link. If the receiver is not configured to connect + to any router it does as locally configured. The sender + is always backup router. + + To server: this is never sent to server. + + 3 = To backup router: Sender is normal server or router and it + tells to backup router that they have connected to the + primary router. Backup router never sends this type. + + 4 = To router: Ending backup resuming protocol. This is sent + to the real primary router to tell that it can take over + the task as being primary router. + + To server: same as sending for router. + + Backup router sends this also to the primary route but only + after it has sent them to normal servers and has purged all + traffic coming from normal servers. + + 5 = To router: Sender is the real primary router after it has + received type 4 from backup router. To tell that it is again + primary router of the cell. + + 20 = To router: This is sent only when router is connecting to + another router and has been replaced by an backup router. + The sender knows that the connectee has been replaced. + + */ + +/* Backup resuming protocol. This protocol is executed when the primary + router wants to resume its position as being primary router. */ + +SILC_TASK_CALLBACK_GLOBAL(silc_server_protocol_backup) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerBackupProtocolContext ctx = protocol->context; + SilcServer server = ctx->server; + SilcBuffer packet; + SilcIDCacheList list; + SilcIDCacheEntry id_cache; + SilcServerEntry server_entry; + int i; + + SILC_LOG_DEBUG(("Start")); + + if (protocol->state == SILC_PROTOCOL_STATE_UNKNOWN) + protocol->state = SILC_PROTOCOL_STATE_START; + + SILC_LOG_DEBUG(("State=%d", protocol->state)); + + switch(protocol->state) { + case SILC_PROTOCOL_STATE_START: + if (ctx->responder == FALSE) { + /* Initiator of the protocol. We are backup router */ + + packet = silc_buffer_alloc(2); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Sending START packets")); + + /* Send the START packet to primary router and normal servers. */ + if (silc_idcache_get_all(server->local_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (!server_entry || (server_entry == server->id_entry) || + !server_entry->connection || !server_entry->data.send_key || + (server_entry->data.status & SILC_IDLIST_STATUS_DISABLED)) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + ctx->sessions = silc_realloc(ctx->sessions, + sizeof(*ctx->sessions) * + (ctx->sessions_count + 1)); + ctx->sessions[ctx->sessions_count].session = ctx->sessions_count; + ctx->sessions[ctx->sessions_count].connected = FALSE; + ctx->sessions[ctx->sessions_count].server_entry = server_entry; + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("START (local) for session %d", + ctx->sessions_count)); + + /* This connection is performing this protocol too now */ + ((SilcSocketConnection)server_entry->connection)->protocol = + protocol; + + if (server_entry->server_type == SILC_ROUTER) + packet->data[0] = SILC_SERVER_BACKUP_START; + else + packet->data[0] = SILC_SERVER_BACKUP_START_GLOBAL; + packet->data[1] = ctx->sessions_count; + silc_server_packet_send(server, server_entry->connection, + SILC_PACKET_RESUME_ROUTER, 0, + packet->data, packet->len, FALSE); + ctx->sessions_count++; + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + + silc_idcache_list_free(list); + } + + if (silc_idcache_get_all(server->global_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (!server_entry || (server_entry == server->id_entry) || + !server_entry->connection || !server_entry->data.send_key || + (server_entry->data.status & SILC_IDLIST_STATUS_DISABLED)) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + ctx->sessions = silc_realloc(ctx->sessions, + sizeof(*ctx->sessions) * + (ctx->sessions_count + 1)); + ctx->sessions[ctx->sessions_count].session = ctx->sessions_count; + ctx->sessions[ctx->sessions_count].connected = FALSE; + ctx->sessions[ctx->sessions_count].server_entry = server_entry; + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("START (global) for session %d", + ctx->sessions_count)); + + /* This connection is performing this protocol too now */ + ((SilcSocketConnection)server_entry->connection)->protocol = + protocol; + + if (server_entry->server_type == SILC_ROUTER) + packet->data[0] = SILC_SERVER_BACKUP_START; + else + packet->data[0] = SILC_SERVER_BACKUP_START_GLOBAL; + packet->data[1] = ctx->sessions_count; + silc_server_packet_send(server, server_entry->connection, + SILC_PACKET_RESUME_ROUTER, 0, + packet->data, packet->len, FALSE); + ctx->sessions_count++; + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + + silc_idcache_list_free(list); + } + + silc_buffer_free(packet); + + /* Announce all of our information */ + silc_server_announce_servers(server, TRUE, 0, ctx->sock); + silc_server_announce_clients(server, 0, ctx->sock); + silc_server_announce_channels(server, 0, ctx->sock); + + protocol->state++; + } else { + /* Responder of the protocol. */ + SilcServerConfigSectionServerConnection *primary; + + /* We should have received START or START_GLOBAL packet */ + if (ctx->type != SILC_SERVER_BACKUP_START && + ctx->type != SILC_SERVER_BACKUP_START_GLOBAL) { + SILC_LOG_DEBUG(("Bad resume router packet")); + break; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Received START packet, reconnecting to router")); + + /* Connect to the primary router that was down that is now supposed + to be back online. We send the CONNECTED packet after we've + established the connection to the primary router. */ + primary = silc_server_config_get_primary_router(server->config); + if (primary && server->backup_primary) { + silc_server_backup_reconnect(server, + primary->host, primary->port, + silc_server_backup_connect_primary, + ctx->sock); + } else { + /* Nowhere to connect just return the CONNECTED packet */ + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Sending CONNECTED packet, session %d", ctx->session)); + + /* Send the CONNECTED packet back to the backup router. */ + packet = silc_buffer_alloc(2); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + silc_buffer_format(packet, + SILC_STR_UI_CHAR(SILC_SERVER_BACKUP_CONNECTED), + SILC_STR_UI_CHAR(ctx->session), + SILC_STR_END); + silc_server_packet_send(server, ctx->sock, + SILC_PACKET_RESUME_ROUTER, 0, + packet->data, packet->len, FALSE); + silc_buffer_free(packet); + } + + if (server->server_type == SILC_ROUTER && + (!server->router || + server->router->data.status & SILC_IDLIST_STATUS_DISABLED)) + protocol->state++; + else + protocol->state = SILC_PROTOCOL_STATE_END; + + ctx->sessions = silc_realloc(ctx->sessions, + sizeof(*ctx->sessions) * + (ctx->sessions_count + 1)); + ctx->sessions[ctx->sessions_count].session = ctx->session; + ctx->sessions_count++; + } + break; + + case 2: + if (ctx->responder == FALSE) { + /* Initiator */ + + /* We should have received CONNECTED packet */ + if (ctx->type != SILC_SERVER_BACKUP_CONNECTED) { + SILC_LOG_DEBUG(("Bad resume router packet")); + break; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Received CONNECTED packet, session %d", ctx->session)); + + for (i = 0; i < ctx->sessions_count; i++) { + if (ctx->sessions[i].session == ctx->session) { + ctx->sessions[i].connected = TRUE; + break; + } + } + + for (i = 0; i < ctx->sessions_count; i++) { + if (!ctx->sessions[i].connected) + return; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Sending ENDING packet to primary")); + + /* Send with a timeout */ + silc_schedule_task_add(server->schedule, 0, + silc_server_backup_send_resumed, + protocol, 1, 0, SILC_TASK_TIMEOUT, + SILC_TASK_PRI_NORMAL); + return; + } else { + /* Responder */ + + /* We should have been received ENDING packet */ + if (ctx->type != SILC_SERVER_BACKUP_ENDING) { + SILC_LOG_DEBUG(("Bad resume router packet")); + break; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Received ENDING packet, sending RESUMED packets")); + + /* This state is received by the primary router but also servers + and perhaps other routers so check that if we are the primary + router of the cell then start sending RESUMED packets. If we + are normal server or one of those other routers then procede + to next state. */ + if (server->router && + !(server->router->data.status & SILC_IDLIST_STATUS_DISABLED) && + silc_server_config_is_primary_route(server->config)) { + /* We'll wait for RESUMED packet */ + protocol->state = SILC_PROTOCOL_STATE_END; + break; + } + + /* Switch announced informations to our primary router of using the + backup router. */ + silc_server_update_servers_by_server(server, ctx->sock->user_data, + server->router); + silc_server_update_clients_by_server(server, ctx->sock->user_data, + server->router, TRUE, FALSE); + + packet = silc_buffer_alloc(2); + silc_buffer_pull_tail(packet, SILC_BUFFER_END(packet)); + + /* We are the primary router, start sending RESUMED packets. */ + if (silc_idcache_get_all(server->local_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (!server_entry || (server_entry == server->id_entry) || + !server_entry->connection || !server_entry->data.send_key) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("RESUMED packet (local)")); + + server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED; + + /* This connection is performing this protocol too now */ + ((SilcSocketConnection)server_entry->connection)->protocol = + protocol; + + if (server_entry->server_type == SILC_ROUTER) + packet->data[0] = SILC_SERVER_BACKUP_RESUMED; + else + packet->data[0] = SILC_SERVER_BACKUP_RESUMED_GLOBAL; + silc_server_packet_send(server, server_entry->connection, + SILC_PACKET_RESUME_ROUTER, 0, + packet->data, packet->len, FALSE); + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + + silc_idcache_list_free(list); + } + + if (silc_idcache_get_all(server->global_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (!server_entry || (server_entry == server->id_entry) || + !server_entry->connection || !server_entry->data.send_key) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("RESUMED packet (global)")); + + server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED; + + /* This connection is performing this protocol too now */ + ((SilcSocketConnection)server_entry->connection)->protocol = + protocol; + + if (server_entry->server_type == SILC_ROUTER) + packet->data[0] = SILC_SERVER_BACKUP_RESUMED; + else + packet->data[0] = SILC_SERVER_BACKUP_RESUMED_GLOBAL; + silc_server_packet_send(server, server_entry->connection, + SILC_PACKET_RESUME_ROUTER, 0, + packet->data, packet->len, FALSE); + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + + silc_idcache_list_free(list); + } + + silc_buffer_free(packet); + + SILC_LOG_INFO(("We are now the primary router of our cell again")); + + /* For us this is the end of this protocol. */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + } + break; + + case SILC_PROTOCOL_STATE_END: + { + SilcIDListData idata; + SilcServerEntry router, backup_router; + + /* We should have been received RESUMED packet from our primary + router. */ + if (ctx->type != SILC_SERVER_BACKUP_RESUMED && + ctx->type != SILC_SERVER_BACKUP_RESUMED_GLOBAL) { + SILC_LOG_DEBUG(("Bad resume router packet")); + break; + } + + SILC_LOG_DEBUG(("********************************")); + SILC_LOG_DEBUG(("Received RESUMED packet")); + + /* We have now new primary router. All traffic goes there from now on. */ + if (server->backup_router) + server->server_type = SILC_BACKUP_ROUTER; + + router = (SilcServerEntry)ctx->sock->user_data; + if (silc_server_backup_replaced_get(server, router->id, + &backup_router)) { + + if (backup_router == server->router) { + server->id_entry->router = router; + server->router = router; + SILC_LOG_INFO(("Switching back to primary router %s", + server->router->server_name)); + SILC_LOG_DEBUG(("Switching back to primary router %s", + server->router->server_name)); + idata = (SilcIDListData)server->router; + idata->status &= ~SILC_IDLIST_STATUS_DISABLED; + } else { + SILC_LOG_INFO(("Resuming the use of router %s", + router->server_name)); + SILC_LOG_DEBUG(("Resuming the use of router %s", + router->server_name)); + idata = (SilcIDListData)router; + idata->status &= ~SILC_IDLIST_STATUS_DISABLED; + } + + /* Update the client entries of the backup router to the new + router */ + silc_server_update_servers_by_server(server, backup_router, router); + silc_server_update_clients_by_server(server, backup_router, + router, TRUE, FALSE); + silc_server_backup_replaced_del(server, backup_router); + silc_server_backup_add(server, backup_router, + ctx->sock->ip, ctx->sock->port, + backup_router->server_type != SILC_ROUTER ? + TRUE : FALSE); + + /* Announce all of our information to the router. */ + if (server->server_type == SILC_ROUTER) + silc_server_announce_servers(server, FALSE, 0, router->connection); + + /* Announce our clients and channels to the router */ + silc_server_announce_clients(server, 0, router->connection); + silc_server_announce_channels(server, 0, router->connection); + } + + /* Protocol has ended, call the final callback */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + } + break; + + case SILC_PROTOCOL_STATE_ERROR: + /* Protocol has ended, call the final callback */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_FAILURE: + /* Protocol has ended, call the final callback */ + if (protocol->final_callback) + silc_protocol_execute_final(protocol, server->schedule); + else + silc_protocol_free(protocol); + break; + + case SILC_PROTOCOL_STATE_UNKNOWN: + break; + } +} + +SILC_TASK_CALLBACK(silc_server_protocol_backup_done) +{ + SilcProtocol protocol = (SilcProtocol)context; + SilcServerBackupProtocolContext ctx = protocol->context; + SilcServer server = ctx->server; + SilcServerEntry server_entry; + SilcSocketConnection sock; + SilcIDCacheList list; + SilcIDCacheEntry id_cache; + + SILC_LOG_DEBUG(("Start")); + + if (protocol->state == SILC_PROTOCOL_STATE_ERROR || + protocol->state == SILC_PROTOCOL_STATE_FAILURE) { + SILC_LOG_ERROR(("Error occurred during backup router resuming protcool")); + } + + /* Remove this protocol from all server entries that has it */ + if (silc_idcache_get_all(server->local_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + sock = (SilcSocketConnection)server_entry->connection; + + if (sock->protocol == protocol) { + sock->protocol = NULL; + + if (server_entry->data.status & SILC_IDLIST_STATUS_DISABLED) + server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED; + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } + + if (silc_idcache_get_all(server->global_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + sock = (SilcSocketConnection)server_entry->connection; + + if (sock->protocol == protocol) { + sock->protocol = NULL; + + if (server_entry->data.status & SILC_IDLIST_STATUS_DISABLED) + server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED; + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } + + if (ctx->sock->protocol) + ctx->sock->protocol = NULL; + silc_protocol_free(protocol); + silc_free(ctx->sessions); + silc_free(ctx); +} diff --git a/apps/silcd/server_backup.h b/apps/silcd/server_backup.h new file mode 100644 index 00000000..57f6a05d --- /dev/null +++ b/apps/silcd/server_backup.h @@ -0,0 +1,135 @@ +/* + + server_backup.h + + Author: Pekka Riikonen + + Copyright (C) 2001 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; version 2 of the License. + + 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. + +*/ + +#ifndef SERVER_BACKUP_H +#define SERVER_BACKUP_H + +/* Backup resuming protocol types */ +#define SILC_SERVER_BACKUP_START 1 +#define SILC_SERVER_BACKUP_START_GLOBAL 2 +#define SILC_SERVER_BACKUP_CONNECTED 3 +#define SILC_SERVER_BACKUP_ENDING 4 +#define SILC_SERVER_BACKUP_RESUMED 5 +#define SILC_SERVER_BACKUP_RESUMED_GLOBAL 6 +#define SILC_SERVER_BACKUP_REPLACED 20 + +/* Adds the `backup_server' to be one of our backup router. This can be + called multiple times to set multiple backup routers. The `replacing' is + the IP and port that the `backup_router' will replace if the `replacing' + will become unresponsive. If `local' is TRUE then the `backup_server' is + in the local cell, if FALSE it is in some other cell. */ +void silc_server_backup_add(SilcServer server, SilcServerEntry backup_server, + const char *ip, int port, bool local); + +/* Returns backup router for IP and port in `replacing' or NULL if there + does not exist backup router. */ +SilcServerEntry silc_server_backup_get(SilcServer server, + SilcServerID *server_id); + +/* Deletes the backup server `server_entry'. */ +void silc_server_backup_del(SilcServer server, SilcServerEntry server_entry); + +/* Marks the IP address and port from the `server_id' as being replaced + by backup router indicated by the `server'. If the router connects at + a later time we can check whether it has been replaced by an backup + router. */ +void silc_server_backup_replaced_add(SilcServer server, + SilcServerID *server_id, + SilcServerEntry server_entry); + +/* Checks whether the IP address and port from the `server_id' has been + replaced by an backup router. If it has been then this returns TRUE + and the bacup router entry to the `server' pointer if non-NULL. Returns + FALSE if the router is not replaced by backup router. */ +bool silc_server_backup_replaced_get(SilcServer server, + SilcServerID *server_id, + SilcServerEntry *server_entry); + +/* Deletes a replaced host by the set `server_entry. */ +void silc_server_backup_replaced_del(SilcServer server, + SilcServerEntry server_entry); + +/* Broadcast the received packet indicated by `packet' to all of our backup + routers. All router wide information is passed using broadcast packets. + That is why all backup routers need to get this data too. It is expected + that the caller already knows that the `packet' is broadcast packet. */ +void silc_server_backup_broadcast(SilcServer server, + SilcSocketConnection sender, + SilcPacketContext *packet); + +/* A generic routine to send data to all backup routers. If the `sender' + is provided it will indicate the original sender of the packet and the + packet won't be resent to that entity. The `data' is the data that will + be assembled to packet context before sending. The packet will be + encrypted this function. If the `force_send' is TRUE the data is sent + immediately and not put to queue. If `local' is TRUE then the packet + will be sent only to local backup routers inside the cell. If false the + packet can go from one cell to the other. This function has no effect + if there are no any backup routers. */ +void silc_server_backup_send(SilcServer server, + SilcServerEntry sender, + SilcPacketType type, + SilcPacketFlags flags, + unsigned char *data, + uint32 data_len, + bool force_send, + bool local); + +/* Same as silc_server_backup_send but sets a specific Destination ID to + the packet. The Destination ID is indicated by the `dst_id' and the + ID type `dst_id_type'. For example, packets destined to channels must + be sent using this function. */ +void silc_server_backup_send_dest(SilcServer server, + SilcServerEntry sender, + SilcPacketType type, + SilcPacketFlags flags, + void *dst_id, + SilcIdType dst_id_type, + unsigned char *data, + uint32 data_len, + bool force_send, + bool local); + +/* Processes incoming RESUME_ROUTER packet. This can give the packet + for processing to the protocol handler or allocate new protocol if + start command is received. */ +void silc_server_backup_resume_router(SilcServer server, + SilcSocketConnection sock, + SilcPacketContext *packet); + +/* Constantly tries to reconnect to a primary router indicated by the + `ip' and `port'. The `connected' callback will be called when the + connection is created. */ +void silc_server_backup_reconnect(SilcServer server, + const char *ip, uint16 port, + SilcServerConnectRouterCallback callback, + void *context); + +/* Called when we've established connection back to our primary router + when we've acting as backup router and have replaced the primary router + in the cell. This function will start the backup resuming protocol. */ +void silc_server_backup_connected(SilcServer server, + SilcServerEntry server_entry, + void *context); + +/* Backup resuming protocol. This protocol is executed when the primary + router wants to resume its position as being primary router. */ +SILC_TASK_CALLBACK_GLOBAL(silc_server_protocol_backup); + +#endif /* SERVER_BACKUP_H */ diff --git a/apps/silcd/server_internal.h b/apps/silcd/server_internal.h index 3ed7cbeb..a40adec6 100644 --- a/apps/silcd/server_internal.h +++ b/apps/silcd/server_internal.h @@ -4,7 +4,7 @@ Author: Pekka Riikonen - Copyright (C) 1997 - 2000 Pekka Riikonen + Copyright (C) 1997 - 2001 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 @@ -23,29 +23,69 @@ /* Server statistics structure. This holds various statistics about various things. */ -/* XXX TODO */ typedef struct { - + /* Local stats (server and router) */ + uint32 my_clients; /* Locally connected clients */ + uint32 my_servers; /* Locally connected servers */ + uint32 my_routers; /* Locally connected routers */ + uint32 my_channels; /* Locally created channels */ + uint32 my_chanclients; /* Local clients on local channels */ + uint32 my_aways; /* Local clients away (XXX) */ + uint32 my_server_ops; /* Local server operators */ + uint32 my_router_ops; /* Local router operators */ + + /* Global stats (mainly for router) */ + uint32 cell_clients; /* All clients in cell */ + uint32 cell_servers; /* All servers in cell */ + uint32 cell_channels; /* All channels in cell */ + uint32 cell_chanclients; /* All clients on cell's channels */ + uint32 clients; /* All clients */ + uint32 servers; /* All servers */ + uint32 routers; /* All routers */ + uint32 channels; /* All channels */ + uint32 chanclients; /* All clients on channels */ + uint32 server_ops; /* All server operators */ + uint32 router_ops; /* All router operators */ + + /* General */ + uint32 conn_attempts; /* Connection attempts */ + uint32 conn_failures; /* Connection failure */ + uint32 auth_attempts; /* Authentication attempts */ + uint32 auth_failures; /* Authentication failures */ + uint32 packets_sent; /* Sent packets */ + uint32 packets_received; /* Received packets */ } SilcServerStatistics; /* SILC Server Object. */ -typedef struct SilcServerObjectStruct { +struct SilcServerStruct { char *server_name; int server_type; int sock; - int standalone; - int listenning; SilcServerID *id; + unsigned char *id_string; + uint32 id_string_len; SilcIdType id_type; - SilcServerList *id_entry; - /* SILC server task queues */ - SilcTaskQueue io_queue; - SilcTaskQueue timeout_queue; - SilcTaskQueue generic_queue; + bool standalone; /* TRUE if server is standalone, and + does not have connection to network. */ + bool listenning; /* TRUE if server is listenning for + incoming connections. */ + SilcServerEntry id_entry; /* Server's own ID entry */ + SilcServerEntry router; /* Pointer to the primary router */ + unsigned long router_connect; /* Time when router was connected */ + SilcServerBackup backup; /* Backup routers */ + bool backup_router; /* TRUE if this is backup router */ + bool backup_primary; /* TRUE if we've switched our primary + router to a backup router. */ + + /* Current command identifier, 0 not used */ + uint16 cmd_ident; + + /* SILC server scheduler */ + SilcSchedule schedule; /* ID lists. */ SilcIDList local_list; @@ -60,7 +100,9 @@ typedef struct SilcServerObjectStruct { SilcCipher none_cipher; /* Server public key */ - SilcPKCS public_key; + SilcPKCS pkcs; + SilcPublicKey public_key; + SilcPrivateKey private_key; /* Hash objects for general hashing */ SilcHash md5hash; @@ -71,47 +113,65 @@ typedef struct SilcServerObjectStruct { SilcHmac sha1hmac; /* Configuration object */ - SilcConfigServer config; + SilcServerConfig config; /* Random pool */ SilcRng rng; /* Server statistics */ - SilcServerStatistics stats; + SilcServerStatistics stat; + + /* Pending command queue */ + SilcDList pending_commands; + + /* Default parameteres for server */ + SilcServerParams params; #ifdef SILC_SIM - /* SIM (SILC Module) table */ - SilcSimContext **sim; - unsigned int sim_count; + /* SIM (SILC Module) list */ + SilcDList sim; #endif -} SilcServerObject; +}; + +/* Server's heartbeat context */ +typedef struct { + SilcServer server; +} *SilcServerHBContext; + +/* Failure context. This is allocated when failure packet is received. + Failure packets are processed with timeout and data is saved in this + structure. */ +typedef struct { + SilcServer server; + SilcSocketConnection sock; + uint32 failure; +} *SilcServerFailureContext; /* Macros */ /* Registers generic task for file descriptor for reading from network and writing to network. As being generic task the actual task is allocated only once and after that the same task applies to all registered fd's. */ -#define SILC_REGISTER_CONNECTION_FOR_IO(fd) \ -do { \ - SilcTask tmptask = silc_task_register(server->generic_queue, (fd), \ - silc_server_packet_process, \ - context, 0, 0, \ - SILC_TASK_GENERIC, \ - SILC_TASK_PRI_NORMAL); \ - silc_task_set_iotype(tmptask, SILC_TASK_WRITE); \ +#define SILC_REGISTER_CONNECTION_FOR_IO(fd) \ +do { \ + silc_schedule_task_add(server->schedule, (fd), \ + silc_server_packet_process, \ + context, 0, 0, \ + SILC_TASK_GENERIC, \ + SILC_TASK_PRI_NORMAL); \ } while(0) -#define SILC_SET_CONNECTION_FOR_INPUT(fd) \ -do { \ - silc_schedule_set_listen_fd((fd), (1L << SILC_TASK_READ)); \ +#define SILC_SET_CONNECTION_FOR_INPUT(s, fd) \ +do { \ + silc_schedule_set_listen_fd((s), (fd), SILC_TASK_READ); \ } while(0) -#define SILC_SET_CONNECTION_FOR_OUTPUT(fd) \ -do { \ - silc_schedule_set_listen_fd((fd), ((1L << SILC_TASK_READ) | \ - (1L << SILC_TASK_WRITE))); \ +#define SILC_SET_CONNECTION_FOR_OUTPUT(s, fd) \ +do { \ + silc_schedule_set_listen_fd((s), (fd), (SILC_TASK_READ | SILC_TASK_WRITE)); \ } while(0) /* Prototypes */ +SILC_TASK_CALLBACK_GLOBAL(silc_server_rekey_final); #endif diff --git a/apps/silcd/server_util.c b/apps/silcd/server_util.c new file mode 100644 index 00000000..d0488095 --- /dev/null +++ b/apps/silcd/server_util.c @@ -0,0 +1,674 @@ +/* + + server_util.c + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2001 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; version 2 of the License. + + 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 "serverincludes.h" +#include "server_internal.h" + +/* Removes the client from channels and possibly removes the channels + as well. After removing those channels that exist, their channel + keys are regnerated. This is called only by the function + silc_server_remove_clients_by_server. */ + +static void silc_server_remove_clients_channels(SilcServer server, + SilcSocketConnection sock, + SilcClientEntry client, + SilcHashTable channels) +{ + SilcChannelEntry channel; + SilcChannelClientEntry chl; + SilcHashTableList htl; + SilcBuffer clidp; + + SILC_LOG_DEBUG(("Start")); + + if (!client || !client->id) + return; + + clidp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + + /* Remove the client from all channels. The client is removed from + the channels' user list. */ + silc_hash_table_list(client->channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + channel = chl->channel; + + /* Remove channel from client's channel list */ + silc_hash_table_del(client->channels, channel); + + /* Remove channel if there is no users anymore */ + if (server->server_type == SILC_ROUTER && + silc_hash_table_count(channel->user_list) < 2) { + + if (silc_hash_table_find(channels, channel, NULL, NULL)) + silc_hash_table_del(channels, channel); + + if (channel->rekey) + silc_schedule_task_del_by_context(server->schedule, channel->rekey); + + if (silc_idlist_del_channel(server->local_list, channel)) + server->stat.my_channels--; + else + silc_idlist_del_channel(server->global_list, channel); + continue; + } + + /* Remove client from channel's client list */ + silc_hash_table_del(channel->user_list, chl->client); + + /* If there is no global users on the channel anymore mark the channel + as local channel. Do not check if the removed client is local client. */ + if (server->server_type != SILC_ROUTER && channel->global_users && + chl->client->router && !silc_server_channel_has_global(channel)) + channel->global_users = FALSE; + + silc_free(chl); + server->stat.my_chanclients--; + + /* If there is not at least one local user on the channel then we don't + need the channel entry anymore, we can remove it safely. */ + if (server->server_type != SILC_ROUTER && + !silc_server_channel_has_local(channel)) { + + if (silc_hash_table_find(channels, channel, NULL, NULL)) + silc_hash_table_del(channels, channel); + + if (channel->rekey) + silc_schedule_task_del_by_context(server->schedule, channel->rekey); + + if (channel->founder_key) { + /* The founder auth data exists, do not remove the channel entry */ + SilcChannelClientEntry chl2; + SilcHashTableList htl2; + + channel->disabled = TRUE; + + silc_hash_table_list(channel->user_list, &htl2); + while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) { + silc_hash_table_del(chl2->client->channels, channel); + silc_hash_table_del(channel->user_list, chl2->client); + silc_free(chl2); + } + continue; + } + + /* Remove the channel entry */ + if (silc_idlist_del_channel(server->local_list, channel)) + server->stat.my_channels--; + else + silc_idlist_del_channel(server->global_list, channel); + continue; + } + + /* Add the channel to the the channels list to regenerate the + channel key */ + if (!silc_hash_table_find(channels, channel, NULL, NULL)) + silc_hash_table_add(channels, channel, channel); + } + + silc_buffer_free(clidp); +} + +/* This function is used to remove all client entries by the server `entry'. + This is called when the connection is lost to the server. In this case + we must invalidate all the client entries owned by the server `entry'. + If the `server_signoff' is TRUE then the SERVER_SIGNOFF notify is + distributed to our local clients. */ + +bool silc_server_remove_clients_by_server(SilcServer server, + SilcServerEntry entry, + bool server_signoff) +{ + SilcIDCacheList list = NULL; + SilcIDCacheEntry id_cache = NULL; + SilcClientEntry client = NULL; + SilcBuffer idp; + SilcClientEntry *clients = NULL; + uint32 clients_c = 0; + unsigned char **argv = NULL; + uint32 *argv_lens = NULL, *argv_types = NULL, argc = 0; + SilcHashTableList htl; + SilcChannelEntry channel; + SilcHashTable channels; + int i; + + SILC_LOG_DEBUG(("Start")); + + /* Allocate the hash table that holds the channels that require + channel key re-generation after we've removed this server's clients + from the channels. */ + channels = silc_hash_table_alloc(0, silc_hash_ptr, NULL, NULL, NULL, + NULL, NULL, TRUE); + + if (server_signoff) { + idp = silc_id_payload_encode(entry->id, SILC_ID_SERVER); + argv = silc_realloc(argv, sizeof(*argv) * (argc + 1)); + argv_lens = silc_realloc(argv_lens, sizeof(*argv_lens) * (argc + 1)); + argv_types = silc_realloc(argv_types, sizeof(*argv_types) * (argc + 1)); + argv[argc] = silc_calloc(idp->len, sizeof(*argv[0])); + memcpy(argv[argc], idp->data, idp->len); + argv_lens[argc] = idp->len; + argv_types[argc] = argc + 1; + argc++; + silc_buffer_free(idp); + } + + if (silc_idcache_get_all(server->local_list->clients, &list)) { + + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + client = (SilcClientEntry)id_cache->context; + if (!(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + if (client->router != entry) { + if (server_signoff) { + clients = silc_realloc(clients, + sizeof(*clients) * (clients_c + 1)); + clients[clients_c] = client; + clients_c++; + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + if (server_signoff) { + idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + argv = silc_realloc(argv, sizeof(*argv) * (argc + 1)); + argv_lens = silc_realloc(argv_lens, sizeof(*argv_lens) * + (argc + 1)); + argv_types = silc_realloc(argv_types, sizeof(*argv_types) * + (argc + 1)); + argv[argc] = silc_calloc(idp->len, sizeof(*argv[0])); + memcpy(argv[argc], idp->data, idp->len); + argv_lens[argc] = idp->len; + argv_types[argc] = argc + 1; + argc++; + silc_buffer_free(idp); + } + + /* Remove the client entry */ + silc_server_remove_clients_channels(server, NULL, client, channels); + client->data.status &= ~SILC_IDLIST_STATUS_REGISTERED; + id_cache->expire = SILC_ID_CACHE_EXPIRE_DEF; + server->stat.clients--; + if (server->server_type == SILC_ROUTER) + server->stat.cell_clients--; + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } + + if (silc_idcache_get_all(server->global_list->clients, &list)) { + + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + client = (SilcClientEntry)id_cache->context; + if (!(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + if (client->router != entry) { + if (server_signoff && client->connection) { + clients = silc_realloc(clients, + sizeof(*clients) * (clients_c + 1)); + clients[clients_c] = client; + clients_c++; + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + if (server_signoff) { + idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT); + argv = silc_realloc(argv, sizeof(*argv) * (argc + 1)); + argv_lens = silc_realloc(argv_lens, sizeof(*argv_lens) * + (argc + 1)); + argv_types = silc_realloc(argv_types, sizeof(*argv_types) * + (argc + 1)); + argv[argc] = silc_calloc(idp->len, sizeof(*argv[0])); + memcpy(argv[argc], idp->data, idp->len); + argv_lens[argc] = idp->len; + argv_types[argc] = argc + 1; + argc++; + silc_buffer_free(idp); + } + + /* Remove the client entry */ + silc_server_remove_clients_channels(server, NULL, client, channels); + client->data.status &= ~SILC_IDLIST_STATUS_REGISTERED; + id_cache->expire = SILC_ID_CACHE_EXPIRE_DEF; + server->stat.clients--; + if (server->server_type == SILC_ROUTER) + server->stat.cell_clients--; + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } + + /* Send the SERVER_SIGNOFF notify */ + if (server_signoff) { + SilcBuffer args, not; + + /* Send SERVER_SIGNOFF notify to our primary router */ + if (!server->standalone && server->router && + server->router != entry) { + args = silc_argument_payload_encode(1, argv, argv_lens, + argv_types); + silc_server_send_notify_args(server, + server->router->connection, + server->server_type == SILC_SERVER ? + FALSE : TRUE, + SILC_NOTIFY_TYPE_SERVER_SIGNOFF, + argc, args); + silc_buffer_free(args); + } + + /* Send to local clients. We also send the list of client ID's that + is to be removed for those servers that would like to use that list. */ + args = silc_argument_payload_encode(argc, argv, argv_lens, + argv_types); + not = silc_notify_payload_encode_args(SILC_NOTIFY_TYPE_SERVER_SIGNOFF, + argc, args); + silc_server_packet_send_clients(server, clients, clients_c, + SILC_PACKET_NOTIFY, 0, FALSE, + not->data, not->len, FALSE); + + silc_free(clients); + silc_buffer_free(args); + silc_buffer_free(not); + for (i = 0; i < argc; i++) + silc_free(argv[i]); + silc_free(argv); + silc_free(argv_lens); + silc_free(argv_types); + } + + /* We must now re-generate the channel key for all channels that had + this server's client(s) on the channel. As they left the channel we + must re-generate the channel key. */ + silc_hash_table_list(channels, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&channel)) { + if (!silc_server_create_channel_key(server, channel, 0)) + return FALSE; + + /* Do not send the channel key if private channel key mode is set */ + if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) + continue; + + silc_server_send_channel_key(server, NULL, channel, + server->server_type == SILC_ROUTER ? + FALSE : !server->standalone); + } + silc_hash_table_free(channels); + + return TRUE; +} + +static SilcServerEntry +silc_server_update_clients_by_real_server(SilcServer server, + SilcServerEntry from, + SilcClientEntry client, + bool local, + SilcIDCacheEntry client_cache) +{ + SilcServerEntry server_entry; + SilcIDCacheEntry id_cache = NULL; + SilcIDCacheList list; + + if (!silc_idcache_get_all(server->local_list->servers, &list)) + return NULL; + + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (server_entry != from && + SILC_ID_COMPARE(server_entry->id, client->id, + client->id->ip.data_len)) { + SILC_LOG_DEBUG(("Found (local) %s", + silc_id_render(server_entry->id, SILC_ID_SERVER))); + + if (!server_entry->data.send_key && server_entry->router) { + SILC_LOG_DEBUG(("Server not locally connected, use its router")); + /* If the client is not marked as local then move it to local list + since the server is local. */ + if (!local) { + SILC_LOG_DEBUG(("Moving client to local list")); + silc_idcache_add(server->local_list->clients, client_cache->name, + client_cache->id, client_cache->context, + client_cache->expire); + silc_idcache_del_by_context(server->global_list->clients, client); + } + server_entry = server_entry->router; + } else { + /* If the client is not marked as local then move it to local list + since the server is local. */ + if (server_entry->server_type != SILC_BACKUP_ROUTER && !local) { + SILC_LOG_DEBUG(("Moving client to local list")); + silc_idcache_add(server->local_list->clients, client_cache->name, + client_cache->id, client_cache->context, + client_cache->expire); + silc_idcache_del_by_context(server->global_list->clients, client); + } + } + + silc_idcache_list_free(list); + return server_entry; + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + + silc_idcache_list_free(list); + + if (!silc_idcache_get_all(server->global_list->servers, &list)) + return NULL; + + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (server_entry != from && + SILC_ID_COMPARE(server_entry->id, client->id, + client->id->ip.data_len)) { + SILC_LOG_DEBUG(("Found (global) %s", + silc_id_render(server_entry->id, SILC_ID_SERVER))); + + if (!server_entry->data.send_key && server_entry->router) { + SILC_LOG_DEBUG(("Server not locally connected, use its router")); + /* If the client is marked as local then move it to global list + since the server is global. */ + if (local) { + SILC_LOG_DEBUG(("Moving client to global list")); + silc_idcache_add(server->global_list->clients, client_cache->name, + client_cache->id, client_cache->context, + client_cache->expire); + silc_idcache_del_by_context(server->local_list->clients, client); + } + server_entry = server_entry->router; + } else { + /* If the client is marked as local then move it to global list + since the server is global. */ + if (server_entry->server_type != SILC_BACKUP_ROUTER && local) { + SILC_LOG_DEBUG(("Moving client to global list")); + silc_idcache_add(server->global_list->clients, client_cache->name, + client_cache->id, client_cache->context, + client_cache->expire); + silc_idcache_del_by_context(server->local_list->clients, client); + } + } + + silc_idcache_list_free(list); + return server_entry; + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + + silc_idcache_list_free(list); + + return NULL; +} + +/* Updates the clients that are originated from the `from' to be originated + from the `to'. If the `resolve_real_server' is TRUE then this will + attempt to figure out which clients really are originated from the + `from' and which are originated from a server that we have connection + to, when we've acting as backup router. If it is FALSE the `to' will + be the new source. This function also removes the clients that are + *really* originated from `from' if `remove_from' is TRUE. These are + clients that the `from' owns, and not just clients that are behind + the `from'. */ + +void silc_server_update_clients_by_server(SilcServer server, + SilcServerEntry from, + SilcServerEntry to, + bool resolve_real_server, + bool remove_from) +{ + SilcIDCacheList list = NULL; + SilcIDCacheEntry id_cache = NULL; + SilcClientEntry client = NULL; + bool local; + + SILC_LOG_DEBUG(("Start")); + + SILC_LOG_DEBUG(("Updating %s", silc_id_render(from->id, + SILC_ID_SERVER))); + SILC_LOG_DEBUG(("to %s", silc_id_render(to->id, + SILC_ID_SERVER))); + + + local = FALSE; + if (silc_idcache_get_all(server->global_list->clients, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + client = (SilcClientEntry)id_cache->context; + if (!(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + SILC_LOG_DEBUG(("Client (global) %s", + silc_id_render(client->id, SILC_ID_CLIENT))); + if (client->router) + SILC_LOG_DEBUG(("Client->router (global) %s", + silc_id_render(client->router->id, SILC_ID_SERVER))); + + if (client->router == from) { + /* Skip clients that are *really* owned by the `from' */ + if (remove_from && SILC_ID_COMPARE(from->id, client->id, + client->id->ip.data_len)) { + SILC_LOG_DEBUG(("Found really owned client, skip it")); + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + if (resolve_real_server) { + client->router = + silc_server_update_clients_by_real_server(server, from, client, + local, id_cache); + if (!client->router) + client->router = to; + } else { + client->router = to; + } + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } + + local = TRUE; + if (silc_idcache_get_all(server->local_list->clients, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + client = (SilcClientEntry)id_cache->context; + if (!(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) { + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + SILC_LOG_DEBUG(("Client (local) %s", + silc_id_render(client->id, SILC_ID_CLIENT))); + if (client->router) + SILC_LOG_DEBUG(("Client->router (local) %s", + silc_id_render(client->router->id, SILC_ID_SERVER))); + + if (client->router == from) { + /* Skip clients that are *really* owned by the `from' */ + if (remove_from && SILC_ID_COMPARE(from->id, client->id, + client->id->ip.data_len)) { + SILC_LOG_DEBUG(("Found really owned client, skip it")); + if (!silc_idcache_list_next(list, &id_cache)) + break; + else + continue; + } + + if (resolve_real_server) { + client->router = + silc_server_update_clients_by_real_server(server, from, client, + local, id_cache); + if (!client->router) + client->router = from; /* on local list put old from */ + } else { + client->router = to; + } + } + + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } + + if (remove_from) + /* Now remove the clients that are still marked as orignated from the + `from'. These are the clients that really was owned by the `from' and + not just exist behind the `from'. */ + silc_server_remove_clients_by_server(server, from, TRUE); +} + +/* Updates servers that are from `from' to be originated from `to'. This + will also update the server's connection to `to's connection. */ + +void silc_server_update_servers_by_server(SilcServer server, + SilcServerEntry from, + SilcServerEntry to) +{ + SilcIDCacheList list = NULL; + SilcIDCacheEntry id_cache = NULL; + SilcServerEntry server_entry = NULL; + + SILC_LOG_DEBUG(("Start")); + + if (silc_idcache_get_all(server->local_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (server_entry->router == from) { + server_entry->router = to; + server_entry->connection = to->connection; + } + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } + + if (silc_idcache_get_all(server->global_list->servers, &list)) { + if (silc_idcache_list_first(list, &id_cache)) { + while (id_cache) { + server_entry = (SilcServerEntry)id_cache->context; + if (server_entry->router == from) { + server_entry->router = to; + server_entry->connection = to->connection; + } + if (!silc_idcache_list_next(list, &id_cache)) + break; + } + } + silc_idcache_list_free(list); + } +} + +/* Checks whether given channel has global users. If it does this returns + TRUE and FALSE if there is only locally connected clients on the channel. */ + +bool silc_server_channel_has_global(SilcChannelEntry channel) +{ + SilcChannelClientEntry chl; + SilcHashTableList htl; + + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + if (chl->client->router) + return TRUE; + } + + return FALSE; +} + +/* Checks whether given channel has locally connected users. If it does this + returns TRUE and FALSE if there is not one locally connected client. */ + +bool silc_server_channel_has_local(SilcChannelEntry channel) +{ + SilcChannelClientEntry chl; + SilcHashTableList htl; + + silc_hash_table_list(channel->user_list, &htl); + while (silc_hash_table_get(&htl, NULL, (void *)&chl)) { + if (!chl->client->router) + return TRUE; + } + + return FALSE; +} + +/* Returns TRUE if the given client is on the channel. FALSE if not. + This works because we assure that the user list on the channel is + always in up to date thus we can only check the channel list from + `client' which is faster than checking the user list from `channel'. */ + +bool silc_server_client_on_channel(SilcClientEntry client, + SilcChannelEntry channel) +{ + if (!client || !channel) + return FALSE; + + if (silc_hash_table_find(client->channels, channel, NULL, NULL)) + return TRUE; + + return FALSE; +} diff --git a/apps/silcd/server_util.h b/apps/silcd/server_util.h new file mode 100644 index 00000000..00f740ba --- /dev/null +++ b/apps/silcd/server_util.h @@ -0,0 +1,68 @@ +/* + + server_util.h + + Author: Pekka Riikonen + + Copyright (C) 1997 - 2001 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; version 2 of the License. + + 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. + +*/ + +#ifndef SERVER_UTIL_H +#define SERVER_UTIL_H + +/* This function is used to remove all client entries by the server `entry'. + This is called when the connection is lost to the server. In this case + we must invalidate all the client entries owned by the server `entry'. + If the `server_signoff' is TRUE then the SERVER_SIGNOFF notify is + distributed to our local clients. */ +bool silc_server_remove_clients_by_server(SilcServer server, + SilcServerEntry entry, + bool server_signoff); + +/* Updates the clients that are originated from the `from' to be originated + from the `to'. If the `resolve_real_server' is TRUE then this will + attempt to figure out which clients really are originated from the + `from' and which are originated from a server that we have connection + to, when we've acting as backup router. If it is FALSE the `to' will + be the new source. This function also removes the clients that are + *really* originated from `from' if `remove_from' is TRUE. These are + clients that the `from' owns, and not just clients that are behind + the `from'. */ +void silc_server_update_clients_by_server(SilcServer server, + SilcServerEntry from, + SilcServerEntry to, + bool resolve_real_server, + bool remove_from); + +/* Updates servers that are from `from' to be originated from `to'. This + will also update the server's connection to `to's connection. */ +void silc_server_update_servers_by_server(SilcServer server, + SilcServerEntry from, + SilcServerEntry to); + +/* Checks whether given channel has global users. If it does this returns + TRUE and FALSE if there is only locally connected clients on the channel. */ +bool silc_server_channel_has_global(SilcChannelEntry channel); + +/* Checks whether given channel has locally connected users. If it does this + returns TRUE and FALSE if there is not one locally connected client. */ +bool silc_server_channel_has_local(SilcChannelEntry channel); + +/* Returns TRUE if the given client is on the channel. FALSE if not. + This works because we assure that the user list on the channel is + always in up to date thus we can only check the channel list from + `client' which is faster than checking the user list from `channel'. */ +bool silc_server_client_on_channel(SilcClientEntry client, + SilcChannelEntry channel); + +#endif /* SERVER_UTIL_H */ diff --git a/apps/silcd/server_version.c b/apps/silcd/server_version.c index 22984a51..1c64390a 100644 --- a/apps/silcd/server_version.c +++ b/apps/silcd/server_version.c @@ -17,15 +17,8 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ #include "serverincludes.h" +#include "version_internal.h" -const char server_version[] = "27062000"; +const char *server_version = SILC_DIST_VERSION_STRING; diff --git a/apps/silcd/serverconfig.c b/apps/silcd/serverconfig.c index 2f617c26..5aaec489 100644 --- a/apps/silcd/serverconfig.c +++ b/apps/silcd/serverconfig.c @@ -17,156 +17,30 @@ GNU General Public License for more details. */ -/* - * $Id$ - * $Log$ - * Revision 1.1.1.1 2000/06/27 11:36:56 priikone - * Importet from internal CVS/Added Log headers. - * - * - */ +/* $Id$ */ #include "serverincludes.h" #include "server_internal.h" -/* XXX - All possible configuration sections for SILC server. - - - - Format: - - +: - - - - Format: - - +: - - - - Format: - - +: - - - - This section is used to set the server informations. - - Format: - - +::: - - - - This section is used to set the server's administrative information. - - Format: - - +::: - - - - This section is used to set ports the server is listenning. - - Format: - - +:: - - - - This section is used to set various logging files, their paths - and maximum sizes. All the other directives except those defined - below are ignored in this section. Log files are purged after they - reach the maximum set byte size. - - Format: - - +infologfile:: - +errorlogfile:: - - - - This section is used to define connection classes. These can be - used to optimize the server and the connections. - - Format: - - +::: - - - - This section is used to define client authentications. - - Format: - - +:::: - - - - This section is used to define the server's administration - authentications. - - Format: - - +:::: - - - - This section is used to define the server connections to this - server/router. Only routers can have normal server connections. - Normal servers leave this section epmty. The remote server cannot be - older than specified Version ID. - - Format: - - +::::: - - - - This section is used to define the router connections to this - server/router. Both normal server and router can have router - connections. Normal server usually has only one connection while - a router can have multiple. The remote server cannot be older than - specified Version ID. - - Format: - - +::::: - - - - This section is used to deny specific connections to your server. This - can be used to deny both clients and servers. - - Format: - - +: