aboutsummaryrefslogtreecommitdiffstats
path: root/irc_im.c
blob: 42186bc90730bb187776691e12fca1ab7284c578 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#!/usr/bin/env python2.7

import subprocess
import sys
import pexpect
import unittest
import shutil
import os
import hashlib

def openssl(args):
	with open(os.devnull, "w") as devnull:
		proc = subprocess.Popen(['openssl'] + args, stdin=subprocess.PIPE, stderr=devnull)
		for i in range(6):
			proc.stdin.write("\n")
		proc.stdin.close()
		proc.communicate()
def setupSkyped():
	try:
		shutil.rmtree("t/skyped")
	except OSError:
		pass
	os.makedirs("t/skyped")
	cwd = os.getcwd()
	os.chdir("t/skyped")
	try:
		shutil.copyfile("../../skyped.cnf", "skyped.cnf")
		openssl(['req', '-new', '-x509', '-days', '365', '-nodes', '-config', 'skyped.cnf', '-out', 'skyped.cert.pem', '-keyout', 'skyped.key.pem'])
		with open("skyped.conf", "w") as sock:
			sock.write("[skyped]\n")
			sock.write("username = alice\n")
			sock.write("password = %s\n" % hashlib.sha1("foo").hexdigest())
			sock.write("cert = %s/skyped.cert.pem\n" % os.getcwd())
			sock.write("key = %s/skyped.key.pem
  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2010 Wilmer van der Gaast and others                *
  \********************************************************************/

/* Some glue to put the IRC and the IM stuff together.                  */

/*
  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 with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  Suite 330, Boston, MA  02111-1307  USA
*/

#include "bitlbee.h"
#include "dcc.h"

/* IM->IRC callbacks: Simple IM/buddy-related stuff. */

static const struct irc_user_funcs irc_user_im_funcs;

static gboolean bee_irc_user_new( bee_t *bee, bee_user_t *bu )
{
	irc_user_t *iu;
	char nick[MAX_NICK_LENGTH+1], *s;
	
	memset( nick, 0, MAX_NICK_LENGTH + 1 );
	strcpy( nick, nick_get( bu->ic->acc, bu->handle ) );
	
	bu->ui_data = iu = irc_user_new( (irc_t*) bee->ui_data, nick );
	iu->bu = bu;
	
	if( ( s = strchr( bu->handle, '@' ) ) )
	{
		iu->host = g_strdup( s + 1 );
		iu->user = g_strndup( bu->handle, s - bu->handle );
	}
	else if( bu->ic->acc->server )
	{
		iu->host = g_strdup( bu->ic->acc->server );
		iu->user = g_strdup( bu->handle );
		
		/* s/ /_/ ... important for AOL screennames */
		for( s = iu->user; *s; s ++ )
			if( *s == ' ' )
				*s = '_';
	}
	else
	{
		iu->host = g_strdup( bu->ic->acc->prpl->name );
		iu->user = g_strdup( bu->handle );
	}
	
	if( set_getbool( &bee->set, "private" ) )
		iu->flags |= IRC_USER_PRIVATE;
	
	iu->f = &irc_user_im_funcs;
	//iu->last_typing_notice = 0;
	
	return TRUE;
}

static gboolean bee_irc_user_free( bee_t *bee, bee_user_t *bu )
{
	return irc_user_free( bee->ui_data, (irc_user_t *) bu->ui_data );
}

static gboolean bee_irc_user_status( bee_t *bee, bee_user_t *bu, bee_user_t *old )
{
	irc_t *irc = bee->ui_data;
	irc_user_t *iu = bu->ui_data;
	irc_channel_t *ic = irc->default_channel;
	
	/* Do this outside the if below since away state can change without
	   the online state changing. */
	iu->flags &= ~IRC_USER_AWAY;
	if( bu->flags & BEE_USER_AWAY || !( bu->flags & BEE_USER_ONLINE ) )
		iu->flags |= IRC_USER_AWAY;
	
	if( ( bu->flags & BEE_USER_ONLINE ) != ( old->flags & BEE_USER_ONLINE ) )
	{
		if( bu->flags & BEE_USER_ONLINE )
		{
			if( g_hash_table_lookup( irc->watches, iu->key ) )
				irc_send_num( irc, 600, "%s %s %s %d :%s", iu->nick, iu->user,
				              iu->host, (int) time( NULL ), "logged online" );
			
			irc_channel_add_user( ic, iu );
			
			if( set_getbool( &bee->set, "away_devoice" ) )
				irc_channel_user_set_mode( ic, iu, ( bu->flags & BEE_USER_AWAY ) ?
				                           0 : IRC_CHANNEL_USER_VOICE );
		}
		else
		{
			if( g_hash_table_lookup( irc->watches, iu->key ) )
				irc_send_num( irc, 601, "%s %s %s %d :%s", iu->nick, iu->user,
				              iu->host, (int) time( NULL ), "logged offline" );
			
			irc_channel_del_user( ic, iu );
		}
	}
	
	return TRUE;
}

static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at )
{
	irc_t *irc = bee->ui_data;
	irc_channel_t *ic = irc->default_channel;
	irc_user_t *iu = (irc_user_t *) bu->ui_data;
	char *dst, *prefix = NULL;
	char *wrapped, *ts = NULL;
	
	if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) )
		ts = irc_format_timestamp( irc, sent_at );
	
	if( iu->flags & IRC_USER_PRIVATE )
	{
		dst = irc->user->nick;
		prefix = ts;
		ts = NULL;
	}
	else
	{
		dst = ic->name;
		prefix = g_strdup_printf( "%s%s%s", irc->user->nick, set_getstr( &bee->set, "to_char" ), ts ? : "" );
	}
	
	wrapped = word_wrap( msg, 425 );
	irc_send_msg( iu, "PRIVMSG", dst, wrapped, prefix );
	
	g_free( wrapped );
	g_free( prefix );
	g_free( ts );
	
	return TRUE;
}

static gboolean bee_irc_user_typing( bee_t *bee, bee_user_t *bu, uint32_t flags )
{
	irc_t *irc = (irc_t *) bee->ui_data;
	
	if( set_getbool( &bee->set, "typing_notice" ) )
		irc_send_msg_f( (irc_user_t *) bu->ui_data, "PRIVMSG", irc->user->nick,
		                "\001TYPING %d\001", ( flags >> 8 ) & 3 );
	else
		return FALSE;
	
	return TRUE;
}

static gboolean bee_irc_user_fullname( bee_t *bee, bee_user_t *bu )
{
	irc_user_t *iu = (irc_user_t *) bu->ui_data;
	irc_t *irc = (irc_t *) bee->ui_data;
	char *s;
	
	if( iu->fullname != iu->nick )
		g_free( iu->fullname );
	iu->fullname = g_strdup( bu->fullname );
	
	/* Strip newlines (unlikely, but IRC-unfriendly so they must go)
	   TODO(wilmer): Do the same with away msgs again! */
	for( s = iu->fullname; *s; s ++ )
		if( isspace( *s ) ) *s = ' ';
	
	if( ( bu->ic->flags & OPT_LOGGED_IN ) && set_getbool( &bee->set, "display_namechanges" ) )
	{
		char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname );
		irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL );
	}
	
	s = set_getstr( &bu->ic->acc->set, "nick_source" );
	if( strcmp( s, "handle" ) != 0 )
	{
		char *name = g_strdup( bu->fullname );
		
		if( strcmp( s, "first_name" ) == 0 )
		{
			int i;
			for( i = 0; name[i] && !isspace( name[i] ); i ++ ) {}
			name[i] = '\0';
		}
		
		imcb_buddy_nick_hint( bu->ic, bu->handle, name );
		
		g_free( name );
	}
	
	return TRUE;
}

/* IRC->IM calls */

static gboolean bee_irc_user_privmsg( irc_user_t *iu, const char *msg )
{
	if( iu->bu )
		return bee_user_msg( iu->irc->b, iu->bu, msg, 0 );
	else
		return FALSE;
}

static gboolean bee_irc_user_ctcp( irc_user_t *iu, char *const *ctcp )
{
	if( ctcp[1] && g_strcasecmp( ctcp[0], "DCC" ) == 0
	            && g_strcasecmp( ctcp[1], "SEND" ) == 0 )
	{
		if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request )
		{
			file_transfer_t *ft = dcc_request( iu->bu->ic, ctcp );
			if ( ft )
				iu->bu->ic->acc->prpl->transfer_request( iu->bu->ic, ft, iu->bu->handle );
			
			return TRUE;
		}
	}
	else if( g_strcasecmp( ctcp[0], "TYPING" ) == 0 )
	{
		if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->send_typing && ctcp[1] )
		{
			int st = ctcp[1][0];
			if( st >= '0' && st <= '2' )
			{
				st <<= 8;
				iu->bu->ic->acc->prpl->send_typing( iu->bu->ic, iu->bu->handle, st );
			}
			
			return TRUE;
		}
	}
	
	return FALSE;
}

static const struct irc_user_funcs irc_user_im_funcs = {
	bee_irc_user_privmsg,
	bee_irc_user_ctcp,
};


/* IM->IRC: Groupchats */
static const struct irc_channel_funcs irc_channel_im_chat_funcs;

static gboolean bee_irc_chat_new( bee_t *bee, struct groupchat *c )
{
	irc_t *irc = bee->ui_data;
	irc_channel_t *ic;
	char *topic;
	GSList *l;
	int i;
	
	/* Try to find a channel that expects to receive a groupchat.
	   This flag is set by groupchat_stub_invite(). */
	for( l = irc->channels; l; l = l->next )
	{
		ic = l->data;
		if( ic->flags & IRC_CHANNEL_CHAT_PICKME )
			break;
	}
	
	/* If we found none, just generate some stupid name. */
	if( l == NULL ) for( i = 0; i <= 999; i ++ )
	{
		char name[16];
		sprintf( name, "&chat_%03d", i );
		if( ( ic = irc_channel_new( irc, name ) ) )
			break;
	}
	
	if( ic == NULL )
		return FALSE;
	
	c->ui_data = ic;
	ic->data = c;
	ic->f = &irc_channel_im_chat_funcs;
	
	topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title );
	irc_channel_set_topic( ic, topic, irc->root );
	g_free( topic );
	
	return TRUE;
}

static gboolean bee_irc_chat_free( bee_t *bee, struct groupchat *c )
{
	irc_channel_t *ic = c->ui_data;
	
	if( ic->flags & IRC_CHANNEL_JOINED )
		irc_channel_printf( ic, "Cleaning up channel, bye!" );
	
	irc_channel_free( ic );
	
	return TRUE;
}

static gboolean bee_irc_chat_log( bee_t *bee, struct groupchat *c, const char *text )
{
	irc_channel_t *ic = c->ui_data;
	
	irc_channel_printf( ic, "%s", text );
	
	return TRUE;
}

static gboolean bee_irc_chat_msg( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at )
{
	irc_t *irc = bee->ui_data;
	irc_user_t *iu = bu->ui_data;
	irc_channel_t *ic = c->ui_data;
	char *ts = NULL;
	
	if( sent_at > 0 && set_getbool( &bee->set, "display_timestamps" ) )
		ts = irc_format_timestamp( irc, sent_at );
	
	irc_send_msg( iu, "PRIVMSG", ic->name, msg, ts );
	g_free( ts );
	
	return TRUE;
}

static gboolean bee_irc_chat_add_user( bee_t *bee, struct groupchat *c, bee_user_t *bu )
{
	irc_t *irc = bee->ui_data;
	
	irc_channel_add_user( c->ui_data, bu == bee->user ? irc->user : bu->ui_data );
	
	return TRUE;
}

static gboolean bee_irc_chat_remove_user( bee_t *bee, struct groupchat *c, bee_user_t *bu )
{
	irc_t *irc = bee->ui_data;
	
	irc_channel_del_user( c->ui_data, bu == bee->user ? irc->user : bu->ui_data );
	
	return TRUE;
}

static gboolean bee_irc_chat_topic( bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu )
{
	irc_t *irc = bee->ui_data;
	irc_user_t *iu;
	
	if( bu == NULL )
		iu = irc->root;
	else if( bu == bee->user )
		iu = irc->user;
	else
		iu = bu->ui_data;
	
	irc_channel_set_topic( c->ui_data, new, iu );
	
	return TRUE;
}

static gboolean bee_irc_chat_name_hint( bee_t *bee, struct groupchat *c, const char *name )
{
	irc_t *irc = bee->ui_data;
	irc_channel_t *ic = c->ui_data;
	char stripped[MAX_NICK_LENGTH+1], *full_name;
	
	/* Don't rename a channel if the user's in it already. */
	if( ic->flags & IRC_CHANNEL_JOINED )
		return FALSE;
	
	strncpy( stripped, name, MAX_NICK_LENGTH );
	stripped[MAX_NICK_LENGTH] = '\0';
	nick_strip( stripped );
	if( set_getbool( &bee->set, "lcnicks" ) )
		nick_lc( stripped );
	
	full_name = g_strdup_printf( "&%s", stripped );
	
	if( stripped[0] && irc_channel_by_name( irc, full_name ) == NULL )
	{
		g_free( ic->name );
		ic->name = full_name;
	}
	else
	{
		g_free( full_name );
	}
	
	return TRUE;
}

/* IRC->IM */
static gboolean bee_irc_channel_chat_privmsg( irc_channel_t *ic, const char *msg )
{
	struct groupchat *c = ic->data;
	
	bee_chat_msg( ic->irc->b, c, msg, 0 );
	
	return TRUE;
	
}

static gboolean bee_irc_channel_chat_part( irc_channel_t *ic, const char *msg )
{
	struct groupchat *c = ic->data;
	
	if( c->ic->acc->prpl->chat_leave )
		c->ic->acc->prpl->chat_leave( c );
	
	return TRUE;
	
}

static gboolean bee_irc_channel_chat_topic( irc_channel_t *ic, const char *new )
{
	return TRUE;
}

static gboolean bee_irc_channel_chat_invite( irc_channel_t *ic, irc_user_t *iu )
{
	struct groupchat *c = ic->data;
	
	if( iu->bu->ic != c->ic )
		irc_send_num( ic->irc, 482, "%s :Can't mix different IM networks in one groupchat", ic->name );
	else if( c->ic->acc->prpl->chat_invite )
		c->ic->acc->prpl->chat_invite( c, iu->bu->handle, NULL );
	else
		irc_send_num( ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name );
	
	return TRUE;
}

static const struct irc_channel_funcs irc_channel_im_chat_funcs = {
	bee_irc_channel_chat_privmsg,
	NULL, /* join */
	bee_irc_channel_chat_part,
	bee_irc_channel_chat_topic,
	bee_irc_channel_chat_invite,
};


/* IM->IRC: File transfers */
static file_transfer_t *bee_irc_ft_in_start( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size )
{
	return dccs_send_start( bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size );
}

static gboolean bee_irc_ft_out_start( struct im_connection *ic, file_transfer_t *ft )
{
	return dccs_recv_start( ft );
}

static void bee_irc_ft_close( struct im_connection *ic, file_transfer_t *ft )
{
	return dcc_close( ft );
}

static void bee_irc_ft_finished( struct im_connection *ic, file_transfer_t *file )
{
	dcc_file_transfer_t *df = file->priv;

	if( file->bytes_transferred >= file->file_size )
		dcc_finish( file );
	else
		df->proto_finished = TRUE;
}

const struct bee_ui_funcs irc_ui_funcs = {
	bee_irc_user_new,
	bee_irc_user_free,
	bee_irc_user_fullname,
	bee_irc_user_status,
	bee_irc_user_msg,
	bee_irc_user_typing,
	
	bee_irc_chat_new,
	bee_irc_chat_free,
	bee_irc_chat_log,
	bee_irc_chat_msg,
	bee_irc_chat_add_user,
	bee_irc_chat_remove_user,
	bee_irc_chat_topic,
	bee_irc_chat_name_hint,
	
	bee_irc_ft_in_start,
	bee_irc_ft_out_start,
	bee_irc_ft_close,
	bee_irc_ft_finished,
};