aboutsummaryrefslogtreecommitdiffstats
path: root/irc_send.c
blob: 7739f79831c3c8d7fed422f3dd1815f225da4065 (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
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 */
  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2001-2004 Wilmer van der Gaast and others                *
  \********************************************************************/

/* URL/mirror stuff - Stolen from Axel                                  */

/*
  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"

#define PROTO_HTTP      2
#define PROTO_HTTPS     5
#define PROTO_SOCKS4    3
#define PROTO_SOCKS5    4
#define PROTO_DEFAULT   PROTO_HTTP

typedef struct url
{
	int proto;
	int port;
	char host[MAX_STRING+1];
	char file[MAX_STRING+1];
	char user[MAX_STRING+1];
	char pass[MAX_STRING+1];
} url_t;

int url_set( url_t *url, const char *set_url );
href='#n336'>336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2010 Wilmer van der Gaast and others                *
  \********************************************************************/

/* The IRC-based UI - Sending responses to commands/etc.                */

/*
  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"

void irc_send_num( irc_t *irc, int code, char *format, ... )
{
	char text[IRC_MAX_LINE];
	va_list params;
	
	va_start( params, format );
	g_vsnprintf( text, IRC_MAX_LINE, format, params );
	va_end( params );
	
	irc_write( irc, ":%s %03d %s %s", irc->root->host, code, irc->user->nick ? : "*", text );
}

void irc_send_login( irc_t *irc )
{
	irc_send_num( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->user->nick );
	irc_send_num( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->root->host );
	irc_send_num( irc,   3, ":%s", IRCD_INFO );
	irc_send_num( irc,   4, "%s %s %s %s", irc->root->host, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES );
	irc_send_num( irc,   5, "PREFIX=(ohv)@%%+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d CHANNELLEN=%d "
	                        "NETWORK=BitlBee SAFELIST CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 "
	                        "FLOOD=0/9999 :are supported by this server",
	                        CTYPES, CMODES, MAX_NICK_LENGTH - 1, MAX_NICK_LENGTH - 1 );
	irc_send_motd( irc );
}

void irc_send_motd( irc_t *irc )
{
	char motd[2048];
	size_t len;
	int fd;
	
	fd = open( global.conf->motdfile, O_RDONLY );
	if( fd == -1 || ( len = read( fd, motd, sizeof( motd ) - 1 ) ) <= 0 )
	{
		irc_send_num( irc, 422, ":We don't need MOTDs." );
	}
	else
	{
		char linebuf[80];
		char *add = "", max, *in;
		
		in = motd;
		motd[len] = '\0';
		linebuf[79] = len = 0;
		max = sizeof( linebuf ) - 1;
		
		irc_send_num( irc, 375, ":- %s Message Of The Day - ", irc->root->host );
		while( ( linebuf[len] = *(in++) ) )
		{
			if( linebuf[len] == '\n' || len == max )
			{
				linebuf[len] = 0;
				irc_send_num( irc, 372, ":- %s", linebuf );
				len = 0;
			}
			else if( linebuf[len] == '%' )
			{
				linebuf[len] = *(in++);
				if( linebuf[len] == 'h' )
					add = irc->root->host;
				else if( linebuf[len] == 'v' )
					add = BITLBEE_VERSION;
				else if( linebuf[len] == 'n' )
					add = irc->user->nick;
				else if( linebuf[len] == '\0' )
					in --;
				else
					add = "%";
				
				strncpy( linebuf + len, add, max - len );
				while( linebuf[++len] );
			}
			else if( len < max )
			{
				len ++;
			}
		}
		irc_send_num( irc, 376, ":End of MOTD" );
	}
	
	if( fd != -1 )
		close( fd );
}

/* Used by some funcs that generate PRIVMSGs to figure out if we're talking to
   this person in /query or in a control channel. WARNING: callers rely on
   this returning a pointer at irc->user_nick, not a copy of it. */
const char *irc_user_msgdest( irc_user_t *iu )
{
	irc_t *irc = iu->irc;
	irc_channel_t *ic = NULL;

	if( iu->last_channel )
	{
		if( iu->last_channel->flags & IRC_CHANNEL_JOINED )
			ic = iu->last_channel;
		else
			ic = irc_channel_with_user( irc, iu );
	}
	
	if( ic )
		return ic->name;
	else
		return irc->user->nick;
}

/* cmd = "PRIVMSG" or "NOTICE" */
static void irc_usermsg_( const char *cmd, irc_user_t *iu, const char *format, va_list params )
{
	char text[2048];
	const char *dst;
	
	g_vsnprintf( text, sizeof( text ), format, params );
	
	dst = irc_user_msgdest( iu );
	irc_send_msg( iu, cmd, dst, text, NULL );
}

void irc_usermsg(irc_user_t *iu, char *format, ... )
{
	va_list params;
	va_start( params, format );
	irc_usermsg_( "PRIVMSG", iu, format, params );
	va_end( params );
}

void irc_usernotice(irc_user_t *iu, char *format, ... )
{
	va_list params;
	va_start( params, format );
	irc_usermsg_( "NOTICE", iu, format, params );
	va_end( params );
}

void irc_rootmsg( irc_t *irc, char *format, ... )
{
	va_list params;
	va_start( params, format );
	irc_usermsg_( "PRIVMSG", irc->root, format, params );
	va_end( params );
}

void irc_send_join( irc_channel_t *ic, irc_user_t *iu )
{
	irc_t *irc = ic->irc;
	
	irc_write( irc, ":%s!%s@%s JOIN :%s", iu->nick, iu->user, iu->host, ic->name );
	
	if( iu == irc->user )
	{
		irc_write( irc, ":%s MODE %s +%s", irc->root->host, ic->name, ic->mode );
		irc_send_names( ic );
		if( ic->topic && *ic->topic )
			irc_send_topic( ic, FALSE );
	}
}

void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason )
{
	irc_write( ic->irc, ":%s!%s@%s PART %s :%s", iu->nick, iu->user, iu->host, ic->name, reason ? : "" );
}

void irc_send_quit( irc_user_t *iu, const char *reason )
{
	irc_write( iu->irc, ":%s!%s@%s QUIT :%s", iu->nick, iu->user, iu->host, reason ? : "" );
}

void irc_send_kick( irc_channel_t *ic, irc_user_t *iu, irc_user_t *kicker, const char *reason )
{
	irc_write( ic->irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user,
	           kicker->host, ic->name, iu->nick, reason ? : "" );
}

void irc_send_names( irc_channel_t *ic )
{
	GSList *l;
	char namelist[385] = "";
	
	/* RFCs say there is no error reply allowed on NAMES, so when the
	   channel is invalid, just give an empty reply. */
	for( l = ic->users; l; l = l->next )
	{
		irc_channel_user_t *icu = l->data;
		irc_user_t *iu = icu->iu;
		
		if( strlen( namelist ) + strlen( iu->nick ) > sizeof( namelist ) - 4 )
		{
			irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist );
			*namelist = 0;
		}
		
		if( icu->flags & IRC_CHANNEL_USER_OP )
			strcat( namelist, "@" );
		else if( icu->flags & IRC_CHANNEL_USER_HALFOP )
			strcat( namelist, "%" );
		else if( icu->flags & IRC_CHANNEL_USER_VOICE )
			strcat( namelist, "+" );
		
		strcat( namelist, iu->nick );
		strcat( namelist, " " );
	}
	
	if( *namelist )
		irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist );
	
	irc_send_num( ic->irc, 366, "%s :End of /NAMES list", ic->name );
}

void irc_send_topic( irc_channel_t *ic, gboolean topic_change )
{
	if( topic_change && ic->topic_who )
	{
		irc_write( ic->irc, ":%s TOPIC %s :%s", ic->topic_who, 
		           ic->name, ic->topic && *ic->topic ? ic->topic : "" );
	}
	else if( ic->topic )
	{
		irc_send_num( ic->irc, 332, "%s :%s", ic->name, ic->topic );
		if( ic->topic_who )
			irc_send_num( ic->irc, 333, "%s %s %d",
			              ic->name, ic->topic_who, (int) ic->topic_time );
	}
	else
		irc_send_num( ic->irc, 331, "%s :No topic for this channel", ic->name );
}

void irc_send_whois( irc_user_t *iu )
{
	irc_t *irc = iu->irc;
	
	irc_send_num( irc, 311, "%s %s %s * :%s",
	              iu->nick, iu->user, iu->host, iu->fullname );
	
	if( iu->bu )
	{
		bee_user_t *bu = iu->bu;
		
		irc_send_num( irc, 312, "%s %s.%s :%s network", iu->nick, bu->ic->acc->user,
		           bu->ic->acc->server && *bu->ic->acc->server ? bu->ic->acc->server : "",
		           bu->ic->acc->prpl->name );
		
		if( ( bu->status && *bu->status ) ||
		    ( bu->status_msg && *bu->status_msg ) )
		{
			int num = bu->flags & BEE_USER_AWAY ? 301 : 320;
			
			if( bu->status && bu->status_msg )
				irc_send_num( irc, num, "%s :%s (%s)", iu->nick, bu->status, bu->status_msg );
			else
				irc_send_num( irc, num, "%s :%s", iu->nick, bu->status ? : bu->status_msg );
		}
		else if( !( bu->flags & BEE_USER_ONLINE ) )
		{
			irc_send_num( irc, 301, "%s :%s", iu->nick, "User is offline" );
		}
		
		if( bu->idle_time || bu->login_time )
		{
			irc_send_num( irc, 317, "%s %d %d :seconds idle, signon time",
			              iu->nick,
			              bu->idle_time ? (int) ( time( NULL ) - bu->idle_time ) : 0,
			              (int) bu->login_time );
		}
	}
	else
	{
		irc_send_num( irc, 312, "%s %s :%s", iu->nick, irc->root->host, IRCD_INFO );
	}
	
	irc_send_num( irc, 318, "%s :End of /WHOIS list", iu->nick );
}

void irc_send_who( irc_t *irc, GSList *l, const char *channel )
{
	gboolean is_channel = strchr( CTYPES, channel[0] ) != NULL;
	
	while( l )
	{
		irc_user_t *iu = l->data;
		if( is_channel )
			iu = ((irc_channel_user_t*)iu)->iu;
		/* TODO(wilmer): Restore away/channel information here */
		irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s",
		              is_channel ? channel : "*", iu->user, iu->host, irc->root->host,
		              iu->nick, iu->flags & IRC_USER_AWAY ? 'G' : 'H',
		              iu->fullname );
		l = l->next;
	}
	
	irc_send_num( irc, 315, "%s :End of /WHO list", channel );
}

void irc_send_msg( irc_user_t *iu, const char *type, const char *dst, const char *msg, const char *prefix )
{
	char last = 0;
	const char *s = msg, *line = msg;
	char raw_msg[strlen(msg)+1024];
	
	while( !last )
	{
		if( *s == '\r' && *(s+1) == '\n' )
			s++;
		if( *s == '\n' )
		{
			last = s[1] == 0;
		}
		else
		{
			last = s[0] == 0;
		}
		if( *s == 0 || *s == '\n' )
		{
			if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) &&
			    g_strcasecmp( type, "PRIVMSG" ) == 0 )
			{
				strcpy( raw_msg, "\001ACTION " );
				strncat( raw_msg, line + 4, s - line - 4 );
				strcat( raw_msg, "\001" );
				irc_send_msg_raw( iu, type, dst, raw_msg );
			}
			else
			{
				*raw_msg = '\0';
				if( prefix && *prefix )
					strcpy( raw_msg, prefix );
				strncat( raw_msg, line, s - line );
				irc_send_msg_raw( iu, type, dst, raw_msg );
			}
			line = s + 1;
		}
		s ++;
	}
}

void irc_send_msg_raw( irc_user_t *iu, const char *type, const char *dst, const char *msg )
{
	irc_write( iu->irc, ":%s!%s@%s %s %s :%s",
	           iu->nick, iu->user, iu->host, type, dst, msg && *msg ? msg : " " );
}

void irc_send_msg_f( irc_user_t *iu, const char *type, const char *dst, const char *format, ... )
{
	char text[IRC_MAX_LINE];
	va_list params;
	
	va_start( params, format );
	g_vsnprintf( text, IRC_MAX_LINE, format, params );
	va_end( params );
	
	irc_write( iu->irc, ":%s!%s@%s %s %s :%s",
	           iu->nick, iu->user, iu->host, type, dst, text );
}

void irc_send_nick( irc_user_t *iu, const char *new )
{
	irc_write( iu->irc, ":%s!%s@%s NICK %s",
	           iu->nick, iu->user, iu->host, new );
}

/* Send an update of a user's mode inside a channel, compared to what it was. */
void irc_send_channel_user_mode_diff( irc_channel_t *ic, irc_user_t *iu,
	irc_channel_user_flags_t old, irc_channel_user_flags_t new )
{
	char changes[3*(5+strlen(iu->nick))];
	char from[strlen(ic->irc->root->nick)+strlen(ic->irc->root->user)+strlen(ic->irc->root->host)+3];
	int n;
	
	*changes = '\0'; n = 0;
	if( ( old & IRC_CHANNEL_USER_OP ) != ( new & IRC_CHANNEL_USER_OP ) )
	{
		n ++;
		if( new & IRC_CHANNEL_USER_OP )
			strcat( changes, "+o" );
		else
			strcat( changes, "-o" );
	}
	if( ( old & IRC_CHANNEL_USER_HALFOP ) != ( new & IRC_CHANNEL_USER_HALFOP ) )
	{
		n ++;
		if( new & IRC_CHANNEL_USER_HALFOP )
			strcat( changes, "+h" );
		else
			strcat( changes, "-h" );
	}
	if( ( old & IRC_CHANNEL_USER_VOICE ) != ( new & IRC_CHANNEL_USER_VOICE ) )
	{
		n ++;
		if( new & IRC_CHANNEL_USER_VOICE )
			strcat( changes, "+v" );
		else
			strcat( changes, "-v" );
	}
	while( n )
	{
		strcat( changes, " " );
		strcat( changes, iu->nick );
		n --;
	}
	
	if( set_getbool( &ic->irc->b->set, "simulate_netsplit" ) )
		g_snprintf( from, sizeof( from ), "%s", ic->irc->root->host );
	else
		g_snprintf( from, sizeof( from ), "%s!%s@%s", ic->irc->root->nick,
		            ic->irc->root->user, ic->irc->root->host );
	
	if( *changes )
		irc_write( ic->irc, ":%s MODE %s %s", from, ic->name, changes );
}

void irc_send_invite( irc_user_t *iu, irc_channel_t *ic )
{
	irc_t *irc = iu->irc;
	
	irc_write( iu->irc, ":%s!%s@%s INVITE %s :%s",
	           iu->nick, iu->user, iu->host, irc->user->nick, ic->name );
}