#import "MVChatConnection.h" #import "MVSILCChatConnection.h" #import "MVFileTransfer.h" #import "MVChatPluginManager.h" #import "NSAttributedStringAdditions.h" #import "NSColorAdditions.h" #import "NSMethodSignatureAdditions.h" #import "NSNotificationAdditions.h" #import "NSDataAdditions.h" static SilcPKCS silcPkcs; static SilcPublicKey silcPublicKey; static SilcPrivateKey silcPrivateKey; NSString *MVSILCChatConnectionLoadedCertificate = @"MVSILCChatConnectionLoadedCertificate"; @interface MVSILCChatConnection (MVSILCChatConnectionPrivate) + (const char *) _flattenedSILCStringForMessage:(NSAttributedString *) message; - (SilcClient) _silcClient; - (NSRecursiveLock *) _silcClientLock; - (void) _setSilcConn:(SilcClientConnection)aSilcConn; - (SilcClientConnection) _silcConn; - (NSMutableArray *) _joinedChannels; - (void) _addChannel:(NSString *)channel_name; - (void) _addUser:(NSString *)nick_name toChannel:(NSString *)channel_name withMode:(NSNumber *)mode; - (void) _delUser:(NSString *)nick_name fromChannel:(NSString *)channel_name; - (void) _delUser:(NSString *)nick_name; - (void) _delChannel:(NSString *)channel_name; - (void) _userChangedNick:(NSString *)old_nick_name to:(NSString *)new_nick_name; - (void) _userModeChanged:(NSString *)nick_name onChannel:(NSString *)channel_name toMode:(NSNumber *)mode; - (NSArray *) _getChannelsForUser:(NSString *)nick_name; - (NSMutableDictionary *) _getChannel:(NSString *)channel_name; - (NSNumber *) _getModeForUser:(NSString *)nick_name onChannel:(NSString *)channel_name; - (NSMutableArray *) _queuedCommands; - (NSLock *) _queuedCommandsLock; - (BOOL) _loadKeyPair; - (BOOL) _isKeyPairLoaded; - (void) _connectKeyPairLoaded:(NSNotification *) notification; - (void) _addCommand:(NSString *)raw forNumber:(SilcUInt16) cmd_ident; - (NSString *) _getCommandForNumber:(SilcUInt16) cmd_ident; - (NSLock *) _sentCommandsLock; - (NSMutableDictionary *) _sentCommands; - (void) _sendCommandSucceededNotify:(NSString *) message; - (void) _sendCommandFailedNotify:(NSString *) message; @end #pragma mark - @interface MVChatConnection (MVChatConnectionPrivate) - (void) _willConnect; - (void) _didConnect; - (void) _didNotConnect; - (void) _willDisconnect; - (void) _didDisconnect; @end #pragma mark - void silc_privmessage_resolve_callback( SilcClient client, SilcClientConnection conn, SilcClientEntry *clients, SilcUInt32 clients_count, void *context ) { NSMutableDictionary *dict = context; MVSILCChatConnection *self = [dict objectForKey:@"connection"]; if( ! clients_count ) { goto out; } else { char *nickname = NULL; SilcClientEntry target; if( clients_count > 1 ) { silc_parse_userfqdn( [[dict objectForKey:@"user"] UTF8String], &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( client, conn, nickname, [[dict objectForKey:@"user"] UTF8String], &clients_count); silc_free( nickname ); nickname = NULL; if( ! clients ) goto out; } target = clients[0]; /* Still check for exact math for nickname, this compares the real (formatted) nickname and the nick (maybe formatted) that use gave. This is to assure that `nick' does not match `nick@host'. */ if( ! [[dict objectForKey:@"user"] isEqualToString:[NSString stringWithUTF8String:target -> nickname]] ) { goto out; } [[self _silcClientLock] lock]; silc_client_send_private_message( [self _silcClient], [self _silcConn], target, [[dict objectForKey:@"flags"] intValue], (char *) [[[dict objectForKey:@"message"] string] UTF8String], strlen( [[[dict objectForKey:@"message"] string] UTF8String] ), false ); [[self _silcClientLock] unlock]; } out: [dict release]; } static void silc_nickname_format_parse( const char *nickname, char **ret_nickname ) { silc_parse_userfqdn( nickname, ret_nickname, NULL ); } void silc_channel_get_clients_per_list_callback( SilcClient client, SilcClientConnection conn, SilcClientEntry *clients, SilcUInt32 clients_count, void *context ) { NSDictionary *dict = context; MVSILCChatConnection *self = [dict objectForKey:@"connection"]; NSMutableArray *nickArray = [NSMutableArray arrayWithCapacity:clients_count]; [[self _silcClientLock] lock]; SilcChannelEntry channel = silc_client_get_channel( [self _silcClient], [self _silcConn], (char *) [[dict objectForKey:@"channel_name"] UTF8String]); [[self _silcClientLock] unlock]; int i = 0; for( i = 0; i < clients_count; i++ ) { NSMutableDictionary *info = [NSMutableDictionary dictionary]; NSString *nickname = nil; if ( clients[i] -> nickname ) nickname = [NSString stringWithUTF8String:clients[i] -> nickname]; if ( ! nickname ) { // we can't add a user without its nickname ... continue continue; } [info setObject:nickname forKey:@"nickname"]; SilcChannelUser channelUser = silc_client_on_channel( channel, clients[i] ); BOOL serverOperator = NO; BOOL operator = NO; if( channelUser && channelUser -> mode & SILC_CHANNEL_UMODE_CHANOP ) { operator = YES; } if( clients[i] -> mode & SILC_UMODE_SERVER_OPERATOR || clients[i] -> mode & SILC_UMODE_ROUTER_OPERATOR ) { serverOperator = YES; } [info setObject:[NSNumber numberWithBool:serverOperator] forKey:@"serverOperator"]; [info setObject:[NSNumber numberWithBool:operator] forKey:@"operator"]; [info setObject:[NSNumber numberWithBool:NO] forKey:@"halfOperator"]; [info setObject:[NSNumber numberWithBool:NO] forKey:@"voice"]; if ( clients[i] -> hostname ) [info setObject:[NSString stringWithUTF8String:clients[i] -> hostname] forKey:@"address"]; else [info setObject:@"" forKey:@"address"]; // [info setObject:[NSString stringWithUTF8String:clients[i] -> realname] forKey:@"realName"]; unsigned int mode = 0; if( channelUser ) mode = channelUser -> mode; [self _addUser:nickname toChannel:[dict objectForKey:@"channel_name"] withMode:[NSNumber numberWithUnsignedInt:mode]]; [nickArray addObject:info]; } NSNotification *note = [NSNotification notificationWithName:MVChatConnectionRoomExistingMemberListNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[dict objectForKey:@"channel_name"], @"room", nickArray, @"members", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } static void silc_say( SilcClient client, SilcClientConnection conn, SilcClientMessageType type, char *msg, ... ) { } static void silc_channel_message( SilcClient client, SilcClientConnection conn, SilcClientEntry sender, SilcChannelEntry channel, SilcMessagePayload payload, SilcChannelPrivateKey key, SilcMessageFlags flags, const unsigned char *message, SilcUInt32 message_len) { MVSILCChatConnection *self = conn -> context; BOOL action = NO; if ( flags & SILC_MESSAGE_FLAG_ACTION ) action = YES; NSData *msgData = [NSData dataWithBytes:message length:message_len]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotRoomMessageNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel -> channel_name], @"room", [NSString stringWithUTF8String:sender -> nickname], @"from", msgData, @"message", [NSNumber numberWithBool:action], @"action", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } static void silc_private_message( SilcClient client, SilcClientConnection conn, SilcClientEntry sender, SilcMessagePayload payload, SilcMessageFlags flags, const unsigned char *message, SilcUInt32 message_len ) { MVSILCChatConnection *self = conn -> context; BOOL action = NO; if ( flags & SILC_MESSAGE_FLAG_ACTION ) action = YES; NSData *msgData = [NSData dataWithBytes:message length:message_len]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotPrivateMessageNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:sender -> nickname], @"from", msgData, @"message", [NSNumber numberWithBool:action], @"action", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } static void silc_notify( SilcClient client, SilcClientConnection conn, SilcNotifyType type, ... ) { va_list list; MVSILCChatConnection *self = conn -> context; va_start( list, type ); switch( type ) { case SILC_NOTIFY_TYPE_MOTD: break; case SILC_NOTIFY_TYPE_NONE: break; case SILC_NOTIFY_TYPE_SIGNOFF: { SilcClientEntry signoff_client = va_arg( list, SilcClientEntry ); char *signoff_message = va_arg( list, char * ); if( ! signoff_message ) signoff_message = ""; NSString *nickname = nil; if ( signoff_client -> nickname ) nickname = [NSString stringWithUTF8String:signoff_client -> nickname]; if ( ! nickname ) nickname = @"UNKNOWN"; NSString *hostname = nil; if ( signoff_client -> hostname ) hostname = [NSString stringWithUTF8String:signoff_client -> hostname]; if ( ! hostname ) hostname = @""; NSData *msgData = [NSData dataWithBytes:signoff_message length:strlen( signoff_message )]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionUserQuitNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:nickname, @"who", hostname, @"address", msgData, @"reason", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; [self _delUser:nickname]; } break; case SILC_NOTIFY_TYPE_NICK_CHANGE: { SilcClientEntry oldclient = va_arg( list, SilcClientEntry ); SilcClientEntry newclient = va_arg( list, SilcClientEntry ); // we can't change the nick if any of these two doesn't exist - we return for now if ( ! oldclient -> nickname || ! newclient -> nickname) break; NSString *oldnick = [NSString stringWithUTF8String:oldclient -> nickname]; NSString *newnick = [NSString stringWithUTF8String:newclient -> nickname]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionUserNicknameChangedNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:oldnick, @"oldNickname", newnick, @"newNickname", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; [self _userChangedNick:oldnick to:newnick]; } break; case SILC_NOTIFY_TYPE_SERVER_SIGNOFF: { va_arg( list, void * ); SilcClientEntry *clients = va_arg( list, SilcClientEntry * ); SilcUInt32 clients_count = va_arg( list, int ); int i; if ( ! clients ) break; const char *signoff_message = "Server signoff"; NSData *signoff_data = [NSData dataWithBytes:signoff_message length:strlen( signoff_message )]; for ( i = 0; i < clients_count; i++ ) { SilcClientEntry signoff_client = clients[i]; NSString *nickname = nil; if ( signoff_client -> nickname ) nickname = [NSString stringWithUTF8String:signoff_client -> nickname]; if ( ! nickname ) nickname = @"UNKNOWN"; NSString *hostname = nil; if ( signoff_client -> hostname ) hostname = [NSString stringWithUTF8String:signoff_client -> hostname]; if ( ! hostname ) hostname = @""; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionUserQuitNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:nickname, @"who", hostname, @"address", signoff_data, @"reason", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; [self _delUser:nickname]; } } break; case SILC_NOTIFY_TYPE_WATCH: break; case SILC_NOTIFY_TYPE_JOIN: { SilcClientEntry joining_client = va_arg( list, SilcClientEntry ); SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); if ( ! joining_client || ! channel ) break; if ( ! joining_client -> nickname || ! channel -> channel_name ) break; if ( [[self nickname] isEqualToString:[NSString stringWithUTF8String:joining_client -> nickname]] ) { // we send a notification that we joined the channel in the COMMAND callback, no need to do it here too. break; } NSString *nickname = [NSString stringWithUTF8String:joining_client -> nickname]; NSString *channelname = [NSString stringWithUTF8String:channel -> channel_name]; NSMutableDictionary *info = [NSMutableDictionary dictionary]; [info setObject:nickname forKey:@"nickname"]; SilcChannelUser channelUser = silc_client_on_channel( channel, joining_client ); BOOL serverOperator = NO; BOOL operator = NO; if( channelUser && channelUser -> mode & SILC_CHANNEL_UMODE_CHANOP ) { operator = YES; } if( joining_client -> mode & SILC_UMODE_SERVER_OPERATOR || joining_client -> mode & SILC_UMODE_ROUTER_OPERATOR ) { serverOperator = YES; } [info setObject:[NSNumber numberWithBool:serverOperator] forKey:@"serverOperator"]; [info setObject:[NSNumber numberWithBool:operator] forKey:@"operator"]; [info setObject:[NSNumber numberWithBool:NO] forKey:@"halfOperator"]; [info setObject:[NSNumber numberWithBool:NO] forKey:@"voice"]; NSString *hostname = nil; if ( joining_client -> hostname ) hostname = [NSString stringWithUTF8String:joining_client -> hostname]; if ( hostname ) [info setObject:hostname forKey:@"address"]; NSString *realname = nil; if ( joining_client -> realname ) realname = [NSString stringWithUTF8String:joining_client -> realname]; if ( realname ) [info setObject:realname forKey:@"realName"]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionUserJoinedRoomNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:channelname, @"room", nickname, @"who", info, @"info", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; unsigned int mode = 0; if( channelUser ) mode = channelUser -> mode; [self _addUser:nickname toChannel:channelname withMode:[NSNumber numberWithUnsignedInt:mode]]; } break; case SILC_NOTIFY_TYPE_LEAVE: { SilcClientEntry leaving_client = va_arg( list, SilcClientEntry ); SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); if ( ! leaving_client || ! channel ) break; if ( ! leaving_client -> nickname || ! channel -> channel_name ) break; NSString *hostname; if ( leaving_client -> hostname ) hostname = [NSString stringWithUTF8String:leaving_client -> hostname]; else hostname = @""; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionUserLeftRoomNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel -> channel_name], @"room", [NSString stringWithUTF8String:leaving_client -> nickname], @"who", hostname, @"address", [NSNull null], @"reason", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; [self _delUser:[NSString stringWithUTF8String:leaving_client -> nickname] fromChannel:[NSString stringWithUTF8String:channel -> channel_name]]; } break; case SILC_NOTIFY_TYPE_TOPIC_SET: { SilcIdType setter_id_type = va_arg( list, int ); void *setter_entry = va_arg( list, void * ); char *topic = va_arg( list, char * ); SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); if ( ! setter_entry || ! channel ) break; if ( ! channel -> channel_name ) break; SilcClientEntry client_setter; SilcChannelEntry channel_setter; SilcServerEntry server_setter; NSString *author = nil; switch( setter_id_type ) { case SILC_ID_CLIENT: client_setter = setter_entry; if ( ! client_setter -> nickname ) author = @"Unknown"; else author = [NSString stringWithUTF8String:client_setter -> nickname]; break; case SILC_ID_CHANNEL: channel_setter = setter_entry; if ( ! channel_setter -> channel_name ) author = @"Unknown room"; else author = [NSString stringWithUTF8String:channel_setter -> channel_name]; break; case SILC_ID_SERVER: server_setter = setter_entry; if ( ! server_setter -> server_name ) author = @"Unknown server"; else author = [NSString stringWithUTF8String:server_setter -> server_name]; break; default: author = @"Unknown"; } if( ! topic ) topic = ""; NSData *msgData = [NSData dataWithBytes:topic length:strlen( topic )]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotRoomTopicNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel -> channel_name], @"room", author, @"author", ( msgData ? (id) msgData : (id) [NSNull null] ), @"topic", [NSDate dateWithTimeIntervalSince1970:0], @"time", [NSNumber numberWithBool:NO], @"justJoined", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } break; case SILC_NOTIFY_TYPE_CMODE_CHANGE: break; case SILC_NOTIFY_TYPE_CUMODE_CHANGE: { SilcIdType changer_id_type = va_arg( list, int ); void *changer_entry = va_arg( list, void * ); SilcUInt32 mode = va_arg( list, SilcUInt32 ); SilcClientEntry target_client = va_arg( list, SilcClientEntry ); SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); if ( ! changer_entry || ! target_client || ! channel ) break; if ( ! target_client -> nickname || ! channel -> channel_name ) break; NSString *nickname = [NSString stringWithUTF8String:target_client -> nickname]; NSString *channelname = [NSString stringWithUTF8String:channel -> channel_name]; SilcClientEntry client_changer; SilcChannelEntry channel_changer; SilcServerEntry server_changer; NSString *changer = nil; switch( changer_id_type ) { case SILC_ID_CLIENT: client_changer = changer_entry; if ( ! client_changer -> nickname ) changer = @"Unknown"; else changer = [NSString stringWithUTF8String:client_changer -> nickname]; break; case SILC_ID_CHANNEL: channel_changer = changer_entry; if ( ! channel_changer -> channel_name ) changer = @"Unknown room"; else changer = [NSString stringWithUTF8String:channel_changer -> channel_name]; break; case SILC_ID_SERVER: server_changer = changer_entry; if ( ! server_changer -> server_name ) changer = @"Unknown server"; else changer = [NSString stringWithUTF8String:server_changer -> server_name]; break; default: changer = @"Unknown"; } unsigned int oldmode = [[self _getModeForUser:nickname onChannel:channelname] unsignedIntValue]; BOOL enabled = NO; unsigned int m = MVChatMemberNoModes; /* if( ( oldmode & SILC_CHANNEL_UMODE_CHANFO ) && ! ( mode & SILC_CHANNEL_UMODE_CHANFO ) ) { enabled = NO; m = MVChatMemberOperatorMode; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotMemberModeNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel -> channel_name], @"room", [NSString stringWithUTF8String:target_client -> nickname], @"who", changer, @"by", [NSNumber numberWithBool:enabled], @"enabled", [NSNumber numberWithUnsignedInt:m], @"mode", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } else if( ! ( oldmode & SILC_CHANNEL_UMODE_CHANFO ) && ( mode & SILC_CHANNEL_UMODE_CHANFO ) ) { enabled = YES; m = MVChatMemberOperatorMode; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotMemberModeNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel -> channel_name], @"room", [NSString stringWithUTF8String:target_client -> nickname], @"who", changer, @"by", [NSNumber numberWithBool:enabled], @"enabled", [NSNumber numberWithUnsignedInt:m], @"mode", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } */ if( ( oldmode & SILC_CHANNEL_UMODE_CHANOP ) && ! ( mode & SILC_CHANNEL_UMODE_CHANOP ) ) { enabled = NO; m = MVChatMemberOperatorMode; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotMemberModeNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:channelname, @"room", nickname, @"who", changer, @"by", [NSNumber numberWithBool:enabled], @"enabled", [NSNumber numberWithUnsignedInt:m], @"mode", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } else if( ! ( oldmode & SILC_CHANNEL_UMODE_CHANOP ) && ( mode & SILC_CHANNEL_UMODE_CHANOP ) ) { enabled = YES; m = MVChatMemberOperatorMode; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotMemberModeNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:channelname, @"room", nickname, @"who", changer, @"by", [NSNumber numberWithBool:enabled], @"enabled", [NSNumber numberWithUnsignedInt:m], @"mode", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } [self _userModeChanged:nickname onChannel:channelname toMode:[NSNumber numberWithUnsignedInt:mode]]; } break; case SILC_NOTIFY_TYPE_CHANNEL_CHANGE: break; case SILC_NOTIFY_TYPE_KICKED: { SilcClientEntry kicked = va_arg( list, SilcClientEntry ); char *kick_message = va_arg( list, char * ); SilcClientEntry kicker = va_arg( list, SilcClientEntry ); SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); if ( ! kicked || ! kicker || ! channel ) break; if ( ! kicked -> nickname || ! kicker -> nickname || ! channel -> channel_name ) break; NSString *kickedNickname = [NSString stringWithUTF8String:kicked -> nickname]; NSString *kickerNickname = [NSString stringWithUTF8String:kicker -> nickname]; NSString *channelName = [NSString stringWithUTF8String:channel -> channel_name]; if ( ! kick_message ) kick_message = "No Reason"; NSData *msgData = [NSData dataWithBytes:kick_message length:strlen( kick_message )]; NSNotification *note = nil; if( [[self nickname] isEqualToString:kickedNickname] ) { note = [NSNotification notificationWithName:MVChatConnectionKickedFromRoomNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:channelName, @"room", kickerNickname, @"by", msgData, @"reason", nil]]; } else { note = [NSNotification notificationWithName:MVChatConnectionUserKickedFromRoomNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:channelName, @"room", kickedNickname, @"who", kickerNickname, @"by", msgData, @"reason", nil]]; } [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; [self _delUser:kickedNickname fromChannel:channelName]; } break; case SILC_NOTIFY_TYPE_KILLED: { SilcClientEntry killed = va_arg( list, SilcClientEntry ); char *kill_message = va_arg( list, char * ); SilcIdType killer_type = va_arg( list, int ); void *killer = va_arg( list, void * ); if ( ! killed || ! killer ) break; if ( ! killed -> nickname ) break; NSString *killedNickname = [NSString stringWithUTF8String:killed -> nickname]; SilcClientEntry client_killer; SilcChannelEntry channel_killer; SilcServerEntry server_killer; NSString *killerNickname = nil; switch( killer_type ) { case SILC_ID_CLIENT: client_killer = killer; if ( ! client_killer -> nickname ) killerNickname = @"Unknown"; else killerNickname = [NSString stringWithUTF8String:client_killer -> nickname]; break; case SILC_ID_CHANNEL: channel_killer = killer; if ( ! channel_killer -> channel_name ) killerNickname = @"Unknown room"; else killerNickname = [NSString stringWithUTF8String:channel_killer -> channel_name]; break; case SILC_ID_SERVER: server_killer = killer; if ( ! server_killer -> server_name ) killerNickname = @"Unknown server"; else killerNickname = [NSString stringWithUTF8String:server_killer -> server_name]; break; default: killerNickname = @"Unknown"; } if ( ! kill_message ) kill_message = ""; NSString *killMessage = [NSString stringWithUTF8String:kill_message]; NSString *hostname = nil; if ( killed -> hostname ) hostname = [NSString stringWithUTF8String:killed -> hostname]; else hostname = @""; NSString *quitReason = [NSString stringWithFormat:@"Killed by %@ (%@)", killerNickname, killMessage]; const char *quitReasonString = [quitReason UTF8String]; NSData *msgData = [NSData dataWithBytes:quitReasonString length:strlen( quitReasonString )]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionUserQuitNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:killedNickname, @"who", hostname, @"address", msgData, @"reason", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } break; case SILC_NOTIFY_TYPE_UMODE_CHANGE: break; case SILC_NOTIFY_TYPE_BAN: break; case SILC_NOTIFY_TYPE_ERROR: break; case SILC_NOTIFY_TYPE_INVITE: { SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); char *channel_name = va_arg( list, char * ); SilcClientEntry inviter = va_arg( list, SilcClientEntry ); NSString *channelName = nil; if ( channel && channel -> channel_name ) channelName = [NSString stringWithUTF8String:channel -> channel_name]; if ( ! channelName && channel_name ) channelName = [NSString stringWithUTF8String:channel_name]; if ( ! channelName ) { // we don't get the channel name .. I don't understand this, but silc toolkit documentation // tells us that channel_name *can* be NULL ... we just return ... break; } NSString *by = nil; if ( inviter && inviter -> nickname ) by = [NSString stringWithUTF8String:inviter -> nickname]; if ( ! by ) { break; } NSNotification *note = [NSNotification notificationWithName:MVChatConnectionInvitedToRoomNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:channelName, @"room", by, @"from", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } break; } va_end( list ); } static void silc_command( SilcClient client, SilcClientConnection conn, SilcClientCommandContext cmd_context, bool success, SilcCommand command, SilcStatus status ) { } static void silc_command_reply( SilcClient client, SilcClientConnection conn, SilcCommandPayload cmd_payload, bool success, SilcCommand command, SilcStatus status, ... ) { MVSILCChatConnection *self = conn -> context; va_list list; SilcUInt16 cmdid = silc_command_get_ident( cmd_payload ); NSString *rawCommand = [self _getCommandForNumber:cmdid]; if ( ! rawCommand ) rawCommand = @"Unknown command"; if ( ! success ) { char *error_message = (char *)silc_get_status_message( status ); if ( error_message ) { rawCommand = [rawCommand stringByAppendingFormat:@": %s", error_message]; } [self _sendCommandFailedNotify:rawCommand]; return; } [self _sendCommandSucceededNotify:rawCommand]; va_start( list, status ); switch( command ) { case SILC_COMMAND_WHOIS: { SilcClientEntry client_entry = va_arg( list, SilcClientEntry ); char *nickname = va_arg( list, char * ); char *username = va_arg( list, char * ); char *realname = va_arg( list, char * ); SilcBuffer channels = va_arg( list, SilcBuffer ); /* SilcUInt32 usermode = */ va_arg( list, int ); SilcUInt32 idletime = va_arg( list, int ); /* unsigned char *fingerprint = */ va_arg( list, unsigned char * ); SilcBuffer user_modes = va_arg( list, SilcBuffer ); /* SilcDList attrs = */ va_arg( list, SilcDList ); char *nick = NULL; silc_parse_userfqdn( nickname, &nick, NULL ); NSNotification *note = [NSNotification notificationWithName:MVChatConnectionGotUserWhoisNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:nick], @"who", [NSString stringWithUTF8String:username], @"username", [NSString stringWithUTF8String:client_entry -> hostname], @"hostname", [NSString stringWithUTF8String:realname], @"realname", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; note = [NSNotification notificationWithName:MVChatConnectionGotUserServerNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:nick], @"who", [NSString stringWithUTF8String:client_entry -> server], @"server", [NSString stringWithString:@""], @"serverinfo", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; if( channels && user_modes ) { NSMutableArray *chanArray = [NSMutableArray array]; SilcUInt32 *umodes; SilcDList list = silc_channel_payload_parse_list( channels -> data, channels -> len); if( list && silc_get_mode_list( user_modes, silc_dlist_count( list ), &umodes ) ) { SilcChannelPayload entry; int i = 0; silc_dlist_start( list ); while( ( entry = silc_dlist_get( list ) ) != SILC_LIST_END ) { SilcUInt32 name_len; char *m = silc_client_chumode_char( umodes[i++] ); char *name = silc_channel_get_name( entry, &name_len ); NSMutableString *buf = [NSMutableString string]; if( m ) [buf appendString:[NSString stringWithUTF8String:m]]; [buf appendString:[NSString stringWithUTF8String:name]]; [chanArray addObject:buf], silc_free( m ); } silc_channel_payload_list_free( list ); silc_free( umodes ); } note = [NSNotification notificationWithName:MVChatConnectionGotUserChannelsNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:nick], @"who", chanArray, @"channels", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } note = [NSNotification notificationWithName:MVChatConnectionGotUserIdleNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:nick], @"who", [NSNumber numberWithInt:idletime], @"idle", [NSNumber numberWithInt:0], @"connected", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; note = [NSNotification notificationWithName:MVChatConnectionGotUserWhoisCompleteNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:nick], @"who", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; silc_free( nick ); } break; case SILC_COMMAND_WHOWAS: break; case SILC_COMMAND_IDENTIFY: break; case SILC_COMMAND_NICK: { /*SilcClientEntry local_entry =*/ va_arg( list, SilcClientEntry ); char *nickname = va_arg( list, char * ); /*const SilcClientID *old_client_id =*/ va_arg( list, SilcClientID * ); NSNotification *note = [NSNotification notificationWithName:MVChatConnectionNicknameAcceptedNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:nickname], @"nickname", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } break; case SILC_COMMAND_LIST: { /* SilcChannelEntry channel = */ va_arg( list, SilcChannelEntry ); char *channel_name = va_arg( list, char * ); char *channel_topic = va_arg( list, char * ); SilcUInt32 user_count = va_arg( list, SilcUInt32 ); NSString *r = [NSString stringWithUTF8String:channel_name]; if( ! channel_topic ) channel_topic = ""; NSData *t = [NSData dataWithBytes:channel_topic length:strlen( channel_topic )]; NSMutableDictionary *info = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:user_count], @"users", t, @"topic", [NSDate date], @"cached", r, @"room", nil]; [self performSelectorOnMainThread:@selector( _addRoomToCache: ) withObject:info waitUntilDone:NO]; } break; case SILC_COMMAND_TOPIC: break; case SILC_COMMAND_INVITE: break; case SILC_COMMAND_KILL: break; case SILC_COMMAND_INFO: break; case SILC_COMMAND_STATS: break; case SILC_COMMAND_PING: break; case SILC_COMMAND_OPER: break; case SILC_COMMAND_JOIN: { char *channel_name = va_arg( list, char * ); SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); /* SilcUInt32 channel_mode = */ va_arg( list, SilcUInt32 ); /* int ignored = */ va_arg( list, int ); /* SilcBuffer key_payload = */ va_arg( list, SilcBuffer ); /* void *null1 = */ va_arg( list, void * ); /* void *null2 = */ va_arg( list, void * ); char *topic = va_arg( list, char * ); /* char *hmac_name =*/ va_arg( list, char * ); SilcUInt32 list_count = va_arg( list, SilcUInt32 ); SilcBuffer client_id_list = va_arg( list, SilcBuffer ); /* SilcBuffer client_mode_list = */ va_arg( list, SilcBuffer ); /* SilcPublicKey founder_key = */ va_arg( list, SilcPublicKey ); /* SilcBuffer channel_pubkeys = */ va_arg( list, SilcBuffer ); /* SilcUInt32 user_limit = */ va_arg( list, SilcUInt32 ); NSNotification *note = [NSNotification notificationWithName:MVChatConnectionJoinedRoomNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel_name], @"room", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; if( ! topic ) topic = ""; NSData *msgData = [NSData dataWithBytes:topic length:strlen( topic )]; note = [NSNotification notificationWithName:MVChatConnectionGotRoomTopicNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel -> channel_name], @"room", (id)[NSNull null], @"author", ( msgData ? (id) msgData : (id) [NSNull null] ), @"topic", [NSDate dateWithTimeIntervalSince1970:0], @"time", [NSNumber numberWithBool:YES], @"justJoined", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; [self _addChannel:[NSString stringWithUTF8String:channel_name]]; NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:self, @"connection", [NSString stringWithUTF8String:channel_name], @"channel_name", NULL]; silc_client_get_clients_by_list( [self _silcClient], [self _silcConn], list_count, client_id_list, silc_channel_get_clients_per_list_callback, dict ); } break; case SILC_COMMAND_MOTD: break; case SILC_COMMAND_UMODE: break; case SILC_COMMAND_CMODE: break; case SILC_COMMAND_CUMODE: break; case SILC_COMMAND_KICK: break; case SILC_COMMAND_BAN: break; case SILC_COMMAND_DETACH: break; case SILC_COMMAND_WATCH: break; case SILC_COMMAND_SILCOPER: break; case SILC_COMMAND_LEAVE: { SilcChannelEntry channel = va_arg( list, SilcChannelEntry ); NSNotification *note = [NSNotification notificationWithName:MVChatConnectionLeftRoomNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithUTF8String:channel -> channel_name], @"room", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; [self _delChannel:[NSString stringWithUTF8String:channel -> channel_name]]; } break; case SILC_COMMAND_USERS: break; case SILC_COMMAND_GETKEY: break; } } static void silc_connected( SilcClient client, SilcClientConnection conn, SilcClientConnectionStatus status ) { MVSILCChatConnection *self = conn -> context; [self _setSilcConn:conn]; if( status == SILC_CLIENT_CONN_SUCCESS || status == SILC_CLIENT_CONN_SUCCESS_RESUME ) { [[self _silcClientLock] unlock]; [self performSelectorOnMainThread:@selector( _didConnect ) withObject:nil waitUntilDone:YES]; [[self _silcClientLock] lock]; [[self _queuedCommandsLock] lock]; NSMutableArray *commands = [self _queuedCommands]; if( [commands count] ) { NSEnumerator *enumerator = [commands objectEnumerator]; NSString *command; while( ( command = [enumerator nextObject] ) ) { [self sendRawMessage:command]; } [commands removeAllObjects]; } [[self _queuedCommandsLock] unlock]; } else { [[self _silcClientLock] lock]; silc_client_close_connection( client, conn ); [[self _silcClientLock] unlock]; [self _setSilcConn:NULL]; [self performSelectorOnMainThread:@selector( _didNotConnect ) withObject:nil waitUntilDone:NO]; } } static void silc_disconnected( SilcClient client, SilcClientConnection conn, SilcStatus status, const char *message ) { MVSILCChatConnection *self = conn -> context; [self performSelectorOnMainThread:@selector( _didDisconnect ) withObject:nil waitUntilDone:YES]; } static void silc_get_auth_method( SilcClient client, SilcClientConnection conn, char *hostname, SilcUInt16 port, SilcGetAuthMeth completion, void *context ) { completion( TRUE, SILC_AUTH_NONE, NULL, 0, context ); } static void silc_verify_public_key( SilcClient client, SilcClientConnection conn, SilcSocketType conn_type, unsigned char *pk, SilcUInt32 pk_len, SilcSKEPKType pk_type, SilcVerifyPublicKey completion, void *context ) { // we should ask the user about the servers public key, and save it somewhere if he accepts it completion( TRUE, context ); } static void silc_ask_passphrase( SilcClient client, SilcClientConnection conn, SilcAskPassphrase completion, void *context ) { } static void silc_failure( SilcClient client, SilcClientConnection conn, SilcProtocol protocol, void *failure ) { } static bool silc_key_agreement( SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry, const char *hostname, SilcUInt16 port, SilcKeyAgreementCallback *completion, void **context) { return FALSE; } static void silc_ftp( SilcClient client, SilcClientConnection conn, SilcClientEntry client_entry, SilcUInt32 session_id, const char *hostname, SilcUInt16 port ) { } static void silc_detach( SilcClient client, SilcClientConnection conn, const unsigned char *detach_data, SilcUInt32 detach_data_len ) { } static SilcClientOperations silcClientOps = { silc_say, silc_channel_message, silc_private_message, silc_notify, silc_command, silc_command_reply, silc_connected, silc_disconnected, silc_get_auth_method, silc_verify_public_key, silc_ask_passphrase, silc_failure, silc_key_agreement, silc_ftp, silc_detach }; #pragma mark - @implementation MVSILCChatConnection + (void) initialize { [super initialize]; static BOOL tooLate = NO; if( ! tooLate ) { silc_pkcs_register_default(); silc_hash_register_default(); silc_cipher_register_default(); silc_hmac_register_default(); tooLate = YES; } } #pragma mark - - (id) init { if( ( self = [super init] ) ) { _encoding = NSUTF8StringEncoding; // the only encoding we support memset( &_silcClientParams, 0, sizeof( _silcClientParams ) ); strcat( _silcClientParams.nickname_format, "%n@%h%a" ); _silcClientParams.nickname_parse = silc_nickname_format_parse; _silcClientLock = [[NSRecursiveLock alloc] init]; _silcClient = silc_client_alloc( &silcClientOps, &_silcClientParams, self, NULL ); if( ! _silcClient) { // we need some error handling here.. silc conenction CAN'T work without silc client [self release]; return nil; } [self setUsername:NSUserName()]; [self setRealName:NSFullUserName()]; _joinedChannels = [[NSMutableArray array] retain]; _queuedCommands = [[NSMutableArray array] retain]; _queuedCommandsLock = [[NSLock alloc] init]; _sentCommands = [[NSMutableDictionary dictionary] retain]; _sentCommandsLock = [[NSLock alloc] init]; } return self; } - (void) dealloc { [self disconnect]; [_silcClientLock lock]; if( _silcClient -> realname ) free( _silcClient -> realname ); if( _silcClient -> username ) free( _silcClient -> username ); if( _silcClient -> hostname ) free( _silcClient -> hostname ); if( _silcClient -> nickname ) free( _silcClient -> nickname ); silc_client_free( _silcClient ); [_silcClientLock unlock]; _silcClient = NULL; [_silcClientLock release]; _silcClientLock = nil; [_silcPassword release]; _silcPassword = nil; [_silcServer release]; _silcServer = nil; [_joinedChannels release]; _joinedChannels = nil; [_queuedCommands release]; _queuedCommands = nil; [_queuedCommandsLock release]; _queuedCommandsLock = nil; [_sentCommands release]; _sentCommands = nil; [_sentCommandsLock release]; _sentCommandsLock = nil; [super dealloc]; } #pragma mark - - (MVChatConnectionType) type { return MVChatConnectionSILCType; } - (void) connect { if( [self status] != MVChatConnectionDisconnectedStatus && [self status] != MVChatConnectionServerDisconnectedStatus && [self status] != MVChatConnectionSuspendedStatus ) return; if( ! [self _isKeyPairLoaded] ) { if( ! [self _loadKeyPair] ) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _connectKeyPairLoaded: ) name:MVSILCChatConnectionLoadedCertificate object:nil]; return; } } if( _lastConnectAttempt && ABS( [_lastConnectAttempt timeIntervalSinceNow] ) < 5. ) { // prevents conencting too quick // cancel any reconnect attempts, this lets a user cancel the attempts with a "double connect" [self cancelPendingReconnectAttempts]; return; } [_lastConnectAttempt autorelease]; _lastConnectAttempt = [[NSDate date] retain]; [self _willConnect]; // call early so other code has a chance to change our info _sentQuitCommand = NO; _silcClient -> hostname = strdup( [[[NSProcessInfo processInfo] hostName] UTF8String] ); _silcClient -> pkcs = silcPkcs; _silcClient -> private_key = silcPrivateKey; _silcClient -> public_key = silcPublicKey; if( ! silc_client_init( _silcClient ) ) { // some error, do better reporting [self _didNotConnect]; return; } BOOL errorOnConnect = NO; [_silcClientLock lock]; if( silc_client_connect_to_server( [self _silcClient], NULL, [self serverPort], (char *) [[self server] UTF8String], self ) == -1 ) errorOnConnect = YES; [_silcClientLock unlock]; if( errorOnConnect) [self _didNotConnect]; else [NSThread detachNewThreadSelector:@selector( _silcRunloop ) toTarget:self withObject:nil]; } - (void) disconnectWithReason:(NSAttributedString *) reason { [self cancelPendingReconnectAttempts]; if( [self status] != MVChatConnectionConnectedStatus ) return; _sentQuitCommand = YES; if( [[reason string] length] ) { const char *tmp = [MVSILCChatConnection _flattenedSILCStringForMessage:reason]; [self sendRawMessageWithFormat:@"QUIT %s", tmp]; } else { [self sendRawMessage:@"QUIT"]; } } #pragma mark - - (NSString *) urlScheme { return @"silc"; } #pragma mark - - (void) setEncoding:(NSStringEncoding) encoding { // we don't support encodings other than UTF8. } - (NSStringEncoding) encoding { return NSUTF8StringEncoding; // we don't support encodings other than UTF8. } #pragma mark - - (void) setRealName:(NSString *) name { NSParameterAssert( name != nil ); if( ! _silcClient ) return; if( _silcClient -> realname) free( _silcClient -> realname ); _silcClient -> realname = strdup( [name UTF8String] ); } - (NSString *) realName { if( ! _silcClient ) return nil; return [NSString stringWithUTF8String:_silcClient -> realname]; } #pragma mark - - (void) setNickname:(NSString *) nickname { NSParameterAssert( nickname != nil ); NSParameterAssert( [nickname length] > 0 ); if( ! _silcClient ) return; if( _silcClient -> nickname) free(_silcClient -> nickname); _silcClient -> nickname = strdup( [nickname UTF8String] ); if( [self isConnected] ) { if( ! [nickname isEqualToString:[self nickname]] ) [self sendRawMessageWithFormat:@"NICK %@", nickname]; } } - (NSString *) nickname { if ( [self _silcConn] && [self _silcConn] -> nickname ) return [NSString stringWithUTF8String:[self _silcConn] -> nickname]; return [NSString stringWithUTF8String:_silcClient -> nickname]; } - (NSString *) preferredNickname { return [NSString stringWithUTF8String:_silcClient -> nickname]; } #pragma mark - - (void) setAlternateNicknames:(NSArray *) nicknames { } - (NSArray *) alternateNicknames { return nil; } - (NSString *) nextAlternateNickname { return nil; } #pragma mark - - (void) setNicknamePassword:(NSString *) password { } - (NSString *) nicknamePassword { return nil; } #pragma mark - - (NSString *) certificateServiceName { return @"SILC Keypair"; } - (BOOL) setCertificatePassword:(NSString *) password { [_certificatePassword release]; _certificatePassword = [password copy]; if( _waitForCertificatePassword ) { _waitForCertificatePassword = NO; return [self _loadKeyPair]; } // we don't know if the password is the right one - we return YES anyway return YES; } - (NSString *) certificatePassword { return _certificatePassword; } #pragma mark - - (void) setPassword:(NSString *) password { [_silcPassword release]; if( [password length] ) _silcPassword = [password copy]; else _silcPassword = nil; } - (NSString *) password { return _silcPassword; } #pragma mark - - (void) setUsername:(NSString *) username { NSParameterAssert( username != nil ); NSParameterAssert( [username length] > 0 ); if( ! _silcClient ) return; if( _silcClient -> username ) free( _silcClient -> username ); _silcClient -> username = strdup( [username UTF8String] ); } - (NSString *) username { if( ! _silcClient ) return nil; return [NSString stringWithUTF8String:_silcClient -> username]; } #pragma mark - - (void) setServer:(NSString *) server { [_silcServer release]; _silcServer = [server copy]; } - (NSString *) server { return _silcServer; } #pragma mark - - (void) setServerPort:(unsigned short) port { _silcPort = port; } - (unsigned short) serverPort { return _silcPort; } #pragma mark - - (void) setSecure:(BOOL) ssl { // always secure } - (BOOL) isSecure { return NO; } #pragma mark - - (void) setProxyType:(MVChatConnectionProxy) type { // no proxy support } - (MVChatConnectionProxy) proxyType { return 0; } #pragma mark - - (void) setProxyServer:(NSString *) address { // no proxy support } - (NSString *) proxyServer { return nil; } #pragma mark - - (void) setProxyServerPort:(unsigned short) port { // no proxy support } - (unsigned short) proxyServerPort { return 0; } #pragma mark - - (void) setProxyUsername:(NSString *) username { // no proxy support } - (NSString *) proxyUsername { return nil; } #pragma mark - - (void) setProxyPassword:(NSString *) password { // no proxy support } - (NSString *) proxyPassword { return nil; } #pragma mark - - (void) sendMessage:(NSAttributedString *) message withEncoding:(NSStringEncoding) encoding toUser:(NSString *) user asAction:(BOOL) action { NSParameterAssert( message != nil ); NSParameterAssert( user != nil ); const char *msg = [MVSILCChatConnection _flattenedSILCStringForMessage:message]; SilcMessageFlags flags = SILC_MESSAGE_FLAG_UTF8; if( action) flags |= SILC_MESSAGE_FLAG_ACTION; SilcUInt32 clientsCount; [[self _silcClientLock] lock]; SilcClientEntry *clients = silc_client_get_clients_local( [self _silcClient], [self _silcConn], [user UTF8String], _silcClientParams.nickname_format, &clientsCount ); [[self _silcClientLock] unlock]; if( ! clients || ! clientsCount ) { NSMutableDictionary *dict = [[NSMutableDictionary dictionary] retain]; [dict setObject:message forKey:@"message"]; [dict setObject:user forKey:@"user"]; [dict setObject:self forKey:@"connection"]; [dict setObject:[NSNumber numberWithInt:flags] forKey:@"flags"]; [[self _silcClientLock] lock]; silc_client_get_clients_whois( [self _silcClient], [self _silcConn], [user UTF8String], NULL, NULL, silc_privmessage_resolve_callback, dict ); [[self _silcClientLock] unlock]; return; } [[self _silcClientLock] lock]; silc_client_send_private_message( [self _silcClient], [self _silcConn], clients[0], flags, (char *) msg, strlen( msg ), false ); [[self _silcClientLock] unlock]; } - (void) sendMessage:(NSAttributedString *) message withEncoding:(NSStringEncoding) encoding toChatRoom:(NSString *) room asAction:(BOOL) action { NSParameterAssert( message != nil ); NSParameterAssert( room != nil ); const char *msg = [MVSILCChatConnection _flattenedSILCStringForMessage:message]; SilcMessageFlags flags = SILC_MESSAGE_FLAG_UTF8; if( action ) flags |= SILC_MESSAGE_FLAG_ACTION; [[self _silcClientLock] lock]; SilcChannelEntry channel = silc_client_get_channel( [self _silcClient], [self _silcConn], (char *) [room UTF8String] ); if( ! channel) { [[self _silcClientLock] unlock]; return; } silc_client_send_channel_message( [self _silcClient], [self _silcConn], channel, NULL, flags, (char *) msg, strlen( msg ), false ); [[self _silcClientLock] unlock]; } #pragma mark - - (void) sendRawMessage:(NSString *) raw immediately:(BOOL) now { NSParameterAssert( raw != nil ); if( ! [self isConnected] ) { [[self _queuedCommandsLock] lock]; [[self _queuedCommands] addObject:raw]; [[self _queuedCommandsLock] unlock]; return; } [[self _silcClientLock] lock]; bool b = silc_client_command_call( [self _silcClient], [self _silcConn], [raw UTF8String] ); if ( b ) [self _addCommand:raw forNumber:[self _silcConn] -> cmd_ident]; [[self _silcClientLock] unlock]; if ( ! b ) { [self _sendCommandFailedNotify:raw]; } } #pragma mark - - (MVUploadFileTransfer *) sendFile:(NSString *) path toUser:(NSString *) user { // return [self sendFile:path toUser:user passively:NO]; return nil; } - (MVUploadFileTransfer *) sendFile:(NSString *) path toUser:(NSString *) user passively:(BOOL) passive { // return [[MVUploadFileTransfer transferWithSourceFile:path toUser:user onConnection:self passively:passive] retain]; return nil; } #pragma mark - - (void) sendSubcodeRequest:(NSString *) command toUser:(NSString *) user withArguments:(NSString *) arguments { } - (void) sendSubcodeReply:(NSString *) command toUser:(NSString *) user withArguments:(NSString *) arguments { } #pragma mark - - (void) joinChatRoom:(NSString *) room { NSParameterAssert( room != nil ); NSParameterAssert( [room length] > 0 ); [self sendRawMessageWithFormat:@"JOIN %@", room]; } - (void) partChatRoom:(NSString *) room { NSParameterAssert( room != nil ); NSParameterAssert( [room length] > 0 ); [self sendRawMessageWithFormat:@"LEAVE %@", room]; } #pragma mark - - (NSString *) displayNameFromChatRoom:(NSString *) room { return room; } #pragma mark - - (void) setTopic:(NSAttributedString *) topic withEncoding:(NSStringEncoding) encoding forRoom:(NSString *) room { NSParameterAssert( topic != nil ); NSParameterAssert( room != nil ); [self sendRawMessageWithFormat:@"TOPIC %@ %s", room, [MVSILCChatConnection _flattenedSILCStringForMessage:topic]]; } #pragma mark - - (void) promoteMember:(NSString *) member inRoom:(NSString *) room { NSParameterAssert( member != nil ); NSParameterAssert( room != nil ); [self sendRawMessageWithFormat:@"CUMODE %@ +o %@", room, member]; } - (void) demoteMember:(NSString *) member inRoom:(NSString *) room { NSParameterAssert( member != nil ); NSParameterAssert( room != nil ); [self sendRawMessageWithFormat:@"CUMODE %@ -o %@", room, member]; } - (void) halfopMember:(NSString *) member inRoom:(NSString *) room { } - (void) dehalfopMember:(NSString *) member inRoom:(NSString *) room { } - (void) voiceMember:(NSString *) member inRoom:(NSString *) room { } - (void) devoiceMember:(NSString *) member inRoom:(NSString *) room { } - (void) kickMember:(NSString *) member inRoom:(NSString *) room forReason:(NSString *) reason { NSParameterAssert( member != nil ); NSParameterAssert( room != nil ); if( reason ) [self sendRawMessageWithFormat:@"KICK %@ %@ %@", room, member, reason]; else [self sendRawMessageWithFormat:@"KICK %@ %@", room, member]; } - (void) banMember:(NSString *) member inRoom:(NSString *) room { NSParameterAssert( member != nil ); } - (void) unbanMember:(NSString *) member inRoom:(NSString *) room { NSParameterAssert( member != nil ); } #pragma mark - - (void) addUserToNotificationList:(NSString *) user { NSParameterAssert( user != nil ); } - (void) removeUserFromNotificationList:(NSString *) user { NSParameterAssert( user != nil ); } #pragma mark - - (void) fetchInformationForUser:(NSString *) user withPriority:(BOOL) priority fromLocalServer:(BOOL) localOnly { NSParameterAssert( user != nil ); [self sendRawMessageWithFormat:@"WHOIS %@", user]; } #pragma mark - - (void) fetchRoomList { if( ! _cachedDate || [_cachedDate timeIntervalSinceNow] < -900. ) { [self sendRawMessage:@"LIST"]; [_cachedDate autorelease]; _cachedDate = [[NSDate date] retain]; } } - (void) fetchRoomListWithRooms:(NSArray *) rooms { NSEnumerator *enumerator = [rooms objectEnumerator]; NSString *roomname = [enumerator nextObject]; while (roomname = [enumerator nextObject]) { [self sendRawMessageWithFormat:@"LIST %@", roomname]; } } - (void) stopFetchingRoomList { // can't stop the list } - (NSMutableDictionary *) roomListResults { return [[_roomsCache retain] autorelease]; } #pragma mark - - (NSAttributedString *) awayStatusMessage { return _awayMessage; } - (void) setAwayStatusWithMessage:(NSAttributedString *) message { [_awayMessage autorelease]; _awayMessage = nil; if( [[message string] length] ) { _awayMessage = [message copy]; [self sendRawMessage:@"UMODE +g"]; [[self _silcClientLock] lock]; silc_client_set_away_message( [self _silcClient], [self _silcConn], (char *) [MVSILCChatConnection _flattenedSILCStringForMessage:message] ); [[self _silcClientLock] unlock]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionSelfAwayStatusNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"away", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } else { [self sendRawMessage:@"UMODE -g"]; [[self _silcClientLock] lock]; silc_client_set_away_message( [self _silcClient], [self _silcConn], NULL ); [[self _silcClientLock] unlock]; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionSelfAwayStatusNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], @"away", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; } } - (void) clearAwayStatus { [self setAwayStatusWithMessage:nil]; } #pragma mark - - (unsigned int) lag { return 0; } @end #pragma mark - @implementation MVSILCChatConnection (MVSILCChatConnectionPrivate) + (const char *) _flattenedSILCStringForMessage:(NSAttributedString *) message { NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"NullTerminatedReturn", nil]; NSData *data = [message IRCFormatWithOptions:options]; return [data bytes]; } - (void) _silcRunloop { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; while( [self isConnected] || [self status] == MVChatConnectionConnectingStatus ) { if( [_silcClientLock tryLock] ) { // prevents some deadlocks if( _silcClient && _silcClient -> schedule ) silc_schedule_one( _silcClient -> schedule, 100000 ); // use silc_schedule_one over silc_client_run_one since we want to block a bit inside the locks [_silcClientLock unlock]; } usleep( 500 ); // give time to other threads } [pool release]; } #pragma mark - - (SilcClient) _silcClient { return _silcClient; } - (NSRecursiveLock *) _silcClientLock { return _silcClientLock; } #pragma mark - - (void) _setSilcConn:(SilcClientConnection) aSilcConn { _silcConn = aSilcConn; } - (SilcClientConnection) _silcConn { return _silcConn; } #pragma mark - - (NSMutableArray *) _joinedChannels { return _joinedChannels; } - (void) _addChannel:(NSString *) channel_name { [_joinedChannels addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:channel_name, @"channel_name", NULL]]; } - (void) _addUser:(NSString *) nick_name toChannel:(NSString *) channel_name withMode:(NSNumber *) mode { NSMutableDictionary *channel = [self _getChannel:channel_name]; if( ! channel ) return; NSMutableArray *users = [channel objectForKey:@"users"]; if( ! [users count] ) { users = [NSMutableArray array]; [channel setObject:users forKey:@"users"]; } NSMutableDictionary *user = [NSMutableDictionary dictionary]; [user setObject:nick_name forKey:@"nick_name"]; [user setObject:mode forKey:@"mode"]; [users addObject:user]; } - (void) _delUser:(NSString *)nick_name fromChannel:(NSString *)channel_name { NSMutableDictionary *channel = [self _getChannel:channel_name]; if( ! channel ) return; NSMutableArray *users = [channel objectForKey:@"users"]; if( ! [users count] ) return; NSEnumerator *enumerator = [users objectEnumerator]; NSDictionary *dict = nil; while( ( dict = [enumerator nextObject] ) ) { if( [[dict objectForKey:@"nick_name"] isEqualToString:nick_name]) { [users removeObject:dict]; return; } } } - (void) _delUser:(NSString *) nick_name { NSEnumerator *enumerator = [_joinedChannels objectEnumerator]; NSMutableDictionary *channel = nil; while( ( channel = [enumerator nextObject] ) ) { NSMutableArray *users = [channel objectForKey:@"users"]; if( ! [users count] ) continue; [self _delUser:nick_name fromChannel:[channel objectForKey:@"channel_name"]]; } } - (void) _delChannel:(NSString *) channel_name { NSMutableDictionary *channel = [self _getChannel:channel_name]; if( channel ) [_joinedChannels removeObject:channel]; } - (void) _userChangedNick:(NSString *) old_nick_name to:(NSString *) new_nick_name { NSEnumerator *enumerator = [_joinedChannels objectEnumerator]; NSMutableDictionary *channel = nil; while( ( channel = [enumerator nextObject] ) ) { NSMutableArray *users = [channel objectForKey:@"users"]; if( ! [users count] ) continue; NSEnumerator *userenum = [users objectEnumerator]; NSMutableDictionary *user = nil; while( ( user = [userenum nextObject] ) ) { if( [[user objectForKey:@"nick_name"] isEqualToString:old_nick_name]) { [user setObject:new_nick_name forKey:@"nick_name"]; break; } } } } - (NSArray *) _getChannelsForUser:(NSString *) nick_name { NSEnumerator *enumerator = [_joinedChannels objectEnumerator]; NSMutableDictionary *channel = nil; NSMutableArray *results = [NSMutableArray array]; while( ( channel = [enumerator nextObject] ) ) { NSMutableArray *users = [channel objectForKey:@"users"]; if( ! [users count] ) continue; NSEnumerator *userenum = [users objectEnumerator]; NSMutableDictionary *user = nil; while( ( user = [userenum nextObject] ) ) { if( [[user objectForKey:@"nick_name"] isEqualToString:nick_name]) { [results addObject:channel]; } } } return results; } - (NSMutableDictionary *) _getChannel:(NSString *) channel_name { NSEnumerator *enumerator = [_joinedChannels objectEnumerator]; NSMutableDictionary *dict = nil; while( ( dict = [enumerator nextObject] ) ) { if( [[dict objectForKey:@"channel_name"] isEqualToString:channel_name]) { return dict; } } return nil; } - (NSNumber *) _getModeForUser:(NSString *) nick_name onChannel:(NSString *) channel_name { NSDictionary *channel = [self _getChannel:channel_name]; NSEnumerator *userenum = [[channel objectForKey:@"users"] objectEnumerator]; NSMutableDictionary *user = nil; while( ( user = [userenum nextObject] ) ) { if( [[user objectForKey:@"nick_name"] isEqualToString:nick_name] ) { return [user objectForKey:@"mode"]; } } return nil; } - (void) _userModeChanged:(NSString *) nick_name onChannel:(NSString *) channel_name toMode:(NSNumber *) mode { NSDictionary *channel = [self _getChannel:channel_name]; NSEnumerator *userenum = [[channel objectForKey:@"users"] objectEnumerator]; NSMutableDictionary *user = nil; while( ( user = [userenum nextObject] ) ) { if( [[user objectForKey:@"nick_name"] isEqualToString:nick_name]) { [user setObject:mode forKey:@"mode"]; return; } } } #pragma mark - - (NSMutableArray *) _queuedCommands { return _queuedCommands; } - (NSLock *) _queuedCommandsLock { return _queuedCommandsLock; } #pragma mark - - (BOOL) _loadKeyPair { NSString *publicKeyPath = [[NSString stringWithFormat:@"~/Library/Application Support/Colloquy/Silc/public_key.pub"] stringByExpandingTildeInPath]; NSString *privateKeyPath = [[NSString stringWithFormat:@"~/Library/Application Support/Colloquy/Silc/private_key.prv"] stringByExpandingTildeInPath]; if( ! [[NSFileManager defaultManager] fileExistsAtPath:publicKeyPath] || ! [[NSFileManager defaultManager] fileExistsAtPath:privateKeyPath] ) { // create new keys .. we should propably move this somewhere else.. silc_create_key_pair( NULL, 1024, [publicKeyPath fileSystemRepresentation], [privateKeyPath fileSystemRepresentation], NULL, "", &silcPkcs, &silcPublicKey, &silcPrivateKey, FALSE ); return YES; } BOOL requestPassword = NO; if( ! silc_load_key_pair( [publicKeyPath fileSystemRepresentation], [privateKeyPath fileSystemRepresentation], "", &silcPkcs, &silcPublicKey, &silcPrivateKey ) ) { if( [[self certificatePassword] length] ) { if( ! silc_load_key_pair( [publicKeyPath fileSystemRepresentation], [privateKeyPath fileSystemRepresentation], [[self certificatePassword] UTF8String], &silcPkcs, &silcPublicKey, &silcPrivateKey ) ) requestPassword = YES; } else requestPassword = YES; } if( requestPassword ) { _waitForCertificatePassword = YES; NSNotification *note = [NSNotification notificationWithName:MVChatConnectionNeedCertificatePasswordNotification object:self userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotification:note]; return NO; } NSNotification *note = [NSNotification notificationWithName:MVSILCChatConnectionLoadedCertificate object:self userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:note]; return YES; } - (BOOL) _isKeyPairLoaded { if( ! silcPkcs ) return NO; return YES; } - (void) _connectKeyPairLoaded:(NSNotification *) notification { [[NSNotificationCenter defaultCenter] removeObserver:self name:MVSILCChatConnectionLoadedCertificate object:nil]; [self connect]; } - (void) _addCommand:(NSString *)raw forNumber:(SilcUInt16) cmd_ident { [[self _sentCommandsLock] lock]; [[self _sentCommands] setObject:raw forKey:[NSNumber numberWithUnsignedShort:cmd_ident]]; [[self _sentCommandsLock] unlock]; } - (NSString *) _getCommandForNumber:(SilcUInt16) cmd_ident { NSString *string; NSNumber *number = [NSNumber numberWithUnsignedShort:cmd_ident]; [[self _sentCommandsLock] lock]; string = [[[self _sentCommands] objectForKey:number] retain]; [[self _sentCommands] removeObjectForKey:number]; [[self _sentCommandsLock] unlock]; return [string autorelease]; } - (NSLock *) _sentCommandsLock { return _sentCommandsLock; } - (NSMutableDictionary *) _sentCommands { return _sentCommands; } - (void) _sendCommandSucceededNotify:(NSString *) message { NSNotification *rawMessageNote = [NSNotification notificationWithName:MVChatConnectionGotRawMessageNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:message, @"message", [NSNumber numberWithBool:YES], @"outbound", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:rawMessageNote]; } - (void) _sendCommandFailedNotify:(NSString *) message { NSString *raw = [NSString stringWithFormat:@"Command failed: %@", message]; NSNotification *rawMessageNote = [NSNotification notificationWithName:MVChatConnectionGotRawMessageNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:raw, @"message", [NSNumber numberWithBool:YES], @"outbound", nil]]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThread:rawMessageNote]; } #pragma mark - - (void) _didDisconnect { if( ! _sentQuitCommand ) { if( _status != MVChatConnectionSuspendedStatus ) _status = MVChatConnectionServerDisconnectedStatus; if( ABS( [_lastConnectAttempt timeIntervalSinceNow] ) > 300. ) [self performSelector:@selector( connect ) withObject:nil afterDelay:5.]; [self scheduleReconnectAttemptEvery:30.]; } else if( _status != MVChatConnectionSuspendedStatus ) { _status = MVChatConnectionDisconnectedStatus; } [super _didDisconnect]; [self _setSilcConn:NULL]; } @end