minbif-1.0.5+git20120508/0000755000175000017500000000000011754152023012466 5ustar sebsebminbif-1.0.5+git20120508/scripts/0000755000175000017500000000000011754151773014170 5ustar sebsebminbif-1.0.5+git20120508/scripts/weechat/0000755000175000017500000000000011754151773015610 5ustar sebsebminbif-1.0.5+git20120508/scripts/weechat/facebook_rename.py0000644000175000017500000000712011754151773021262 0ustar sebseb# This script renames your Facebook buddies to a readable format when # using Facebook's XMPP gateway with Minbif or Bitlbee. # Based on the Irssi script at http://browsingtheinternet.com/temp/bitlbee_rename.txt # Ported for Weechat 0.3.0 or later by Jaakko Lintula (crwl@iki.fi) # Modified for Minbif by 'varogami' # Testing contrib 'bizio' # # This program can be distributed under the terms of the GNU GPL3. #Edit this variables with your own minbif configuration settings mode = "minbif" #set 'minbif' or 'bitlbee' for select gateway type minbifChannel = "&minbif" minbifServer = "minbif" bitlbeeChannel = "&bitlbee" bitlbeeServer = "bitlbee" facebookhostname = "chat.facebook.com" minbifBuffer = "%s.%s" % (minbifServer, minbifChannel) bitlbeeBuffer = "%s.%s" % (bitlbeeServer, bitlbeeChannel) nicksToRename = set() import weechat import re weechat.register("facebook_rename", "varogami", "0.0.3", "GPL", "Renames Facebook usernames when using minbif or bitlbee", "", "") def message_join_minbif(data, signal, signal_data): signal_data = signal_data.split() channel = signal_data[2] hostmask = signal_data[0] nick = hostmask[1:hostmask.index('!')] username = hostmask[hostmask.index('!')+1:hostmask.index('@')] server = hostmask[hostmask.index('@')+1:] server = server[:+server.index(':')] if channel == minbifChannel and nick == username and nick[0] == '-' and server == facebookhostname: nicksToRename.add(nick) weechat.command(weechat.buffer_search("irc", minbifBuffer), "/whois "+nick+" "+nick) return weechat.WEECHAT_RC_OK def whois_data_minbif(data, signal, signal_data): if "Full Name" in signal_data: nick = signal_data.split("Full Name:")[0].strip() nick = nick[1:nick.index(' :')] nick = nick.split(' ') nick = nick[3] realname = signal_data.split("Full Name:")[1].strip() if nick in nicksToRename: nicksToRename.remove(nick) ircname = re.sub("[^A-Za-z0-9]", "", realname)[:24] if ircname != nick: weechat.command(weechat.buffer_search("irc", minbifBuffer), "/quote -server %s svsnick %s %s" % (minbifServer, nick, ircname)) return weechat.WEECHAT_RC_OK def message_join_bitlbee(data, signal, signal_data): signal_data = signal_data.split() channel = signal_data[2][1:] hostmask = signal_data[0] nick = hostmask[1:hostmask.index('!')] username = hostmask[hostmask.index('!')+1:hostmask.index('@')] server = hostmask[hostmask.index('@')+1:] if channel == bitlbeeChannel and nick == username and nick[0] == '-' and server == facebookhostname: nicksToRename.add(nick) weechat.command(weechat.buffer_search("irc", bitlbeeBuffer), "/whois " + nick) return weechat.WEECHAT_RC_OK def whois_data_bitlbee(data, signal, signal_data): nick = signal_data.split()[3] realname = signal_data[signal_data.rindex(':')+1:] if nick in nicksToRename: nicksToRename.remove(nick) ircname = re.sub("[^A-Za-z0-9]", "", realname)[:24] if ircname != nick: weechat.command(weechat.buffer_search("irc", bitlbeeBuffer), "/msg %s rename %s %s" % (bitlbeeChannel, nick, ircname)) weechat.command(weechat.buffer_search("irc", bitlbeeBuffer), "/msg %s save" % (bitlbeeChannel)) return weechat.WEECHAT_RC_OK if mode == "minbif": weechat.hook_signal(minbifServer+",irc_in_join", "message_join_minbif", "") weechat.hook_signal(minbifServer+",irc_in_320", "whois_data_minbif", "") if mode == "bitlbee": weechat.hook_signal(bitlbeeServer+",irc_in_join", "message_join_bitlbee", "") weechat.hook_signal(bitlbeeServer+",irc_in_311", "whois_data_bitlbee", "") minbif-1.0.5+git20120508/scripts/weechat/im_typing_notice.rb0000644000175000017500000000742011754151773021500 0ustar sebseb# This script is inspired by "im_typing_notice" for irssi. # It creates a new bar item displaying when contacts are typing on supported protocol in minbif # It sends a notice to your contacts when you're typing a message. # # Author: CissWit cisswit at 6-8 dot fr # Version 1.0 # Licence GPL3 $h_typing = Hash.new $h_sending = Hash.new def weechat_init Weechat.register( "typing_notice", "CissWit", "1.0", "GPL3", "For minbif - displays when someone is typing a message to you, and notice them when you do.", "", "" ) Weechat.bar_item_new("typing_notice", "draw_typing", "") Weechat.hook_modifier("irc_in_privmsg", "modifier_ctcp", "") Weechat.hook_signal("input_text_changed", "input_changed", "") if Weechat.config_is_set_plugin("minbif_server") == 0 Weechat.config_set_plugin("minbif_server", "minbif") end Weechat.print("", "typing_notice\tminbif typing notice") Weechat.print("", "typing_notice\tPut [typing_notice] in your status bar (or the one you prefer) to show when contacts are typing message to you.") return Weechat::WEECHAT_RC_OK end def input_changed(data,signal,type_data) buffer = Weechat.current_buffer buffer_name = Weechat.buffer_get_string buffer, "name" if buffer_name =~ /^#{Weechat.config_get_plugin("minbif_server")}\.(.*)/ nick = $1 if nick == "request" return Weechat::WEECHAT_RC_OK end buffer_text = Weechat.buffer_get_string(buffer,"input") if(buffer_text == "" or buffer_text =~ /^\//) if $h_sending.key?(buffer) Weechat.command(buffer,"/mute all ctcp #{nick} TYPING 0") Weechat.unhook($h_sending[buffer]["timer"]) $h_sending.delete(buffer) end return Weechat::WEECHAT_RC_OK end return Weechat::WEECHAT_RC_OK unless !$h_sending.key?(buffer) Weechat.command(buffer,"/mute -all ctcp #{nick} TYPING 1") if $h_sending.key?(buffer) Weechat.unhook($h_sending[buffer]["timer"]) else $h_sending[buffer] = Hash.new end $h_sending[buffer]["timer"] = Weechat.hook_timer(7000,0,1,"sending_timeout",buffer) $h_sending[buffer]["time"] = Time.new end return Weechat::WEECHAT_RC_OK end def sending_timeout(buffer,n) if $h_sending.key?(buffer) buffer_name = Weechat.buffer_get_string buffer, "name" if buffer_name =~ /^#{Weechat.config_get_plugin("minbif_server")}\.(.*)/ Weechat.command(buffer,"/mute -all ctcp #{$1} TYPING 0") Weechat.unhook($h_sending[buffer]["timer"]) $h_sending.delete(buffer) end end return Weechat::WEECHAT_RC_OK end def draw_typing(osefa,osefb,osefc) buffer = Weechat.current_buffer if $h_typing.key?(buffer) return "TYPING" end return "" end def typing_timeout(buffer,n) if $h_typing.key?(buffer) Weechat.unhook($h_typing[buffer]) $h_typing.delete(buffer) end Weechat.bar_item_update("typing_notice") end def modifier_ctcp(data, modifier, modifier_data, string) if string =~ /:([^!]*)!([^\s]*)\sPRIVMSG\s([^\s]*)\s:\01TYPING\s([0-9])\01/ buffer = Weechat.buffer_search("irc", modifier_data + "." + $1) if $h_typing.key?(buffer) Weechat.unhook($h_typing[buffer]) end if $4 == "1" $h_typing[buffer] = Weechat.hook_timer(7000,0,1,"typing_timeout",buffer) elsif $4 == "0" if $h_typing.key?(buffer) $h_typing.delete(buffer) end elsif $4 == "2" Weechat.print("","- #{$4} - #{$1} - #{buffer} - is typing") end Weechat.bar_item_update("typing_notice") return "" end return string end minbif-1.0.5+git20120508/scripts/irssi/0000755000175000017500000000000011754151773015321 5ustar sebsebminbif-1.0.5+git20120508/scripts/irssi/im_typing_notice.pl0000644000175000017500000002363611754151773021230 0ustar sebseb# SETTINGS & INSTALLATION # [minbif] # /admin typing_notice true # /set typing_notice ON # -> use typing notices # /statusbar window add typing_notice # # [bitlbee] # /set bitlbee_send_typing ON # -> send typing messages to buddies # /set bitlbee_typing_allwin OFF # -> show typing notifications in all windows # /statusbar window add typing_notice # # Changelog: # # 2010-02-09 (version 2.1.2) # * initialize typing_notice to true. # # 2009-12-20 (version 2.1.1) # * reformat installation instructions. # * don't send typing notice unless either bitlbee_send_typing or # typing_notice is true. # # 2009-08-25 (version 2.1) # * externalize some utilities in im_utils.pm # # 2009-08-17 (version 2.0) # * Work with minbif too # # 2006-11-02 (version 1.6.1) # * Sending typing works again. # # 2006-10-27 (version 1.6) # * 'channel sync' re-implemented. # * bitlbee_send_typing was a string setting, It's a boolean now, like it should. # # 2006-10-24 (version 1.5) # # * Sending notices to online users only. # * Using the new get_channel function; # # 2005-12-15 (version 1.42): # * Fixed small bug with typing notices disappearing under certain circumstances # in channels # * Fixed bug that caused outgoing notifications not to work # * root cares not about our typing status. # # 2005-12-04 (version 1.41): # * Implemented stale states in statusbar (shows "(stale)" for OSCAR connections) # * Introduced bitlbee_typing_allwin (default OFF). Set this to ON to make # typing notifications visible in all windows. # # 2005-12-03 (version 1.4): # * Major code cleanups and rewrites for bitlbee 1.0 with the updated typing # scheme. TYPING 0, TYPING 1, and TYPING 2 are now supported from the server. # * Stale states (where user has typed in text but has stopped typing) are now # recognized. # * Bug where user thinks you are still typing if you close the window after # typing something and then erasing it quickly.. fixed. # * If a user signs off while they are still typing, the notification is removed # This update by Matt "f0rked" Sparks # # 2005-08-26: # Some fixes for AIM, Thanks to Dracula. # # 2005-08-16: # AIM supported, for sending notices, using CTCP TYPING 0. (Use the AIM patch from Hanji http://get.bitlbee.org/patches/) # # 2004-10-31: # Sends typing notice to the bitlbee server when typing a message in irssi. bitlbee > 0.92 # # 2004-06-11: # shows [typing: ] in &bitlbee with multiple users. use strict; use Irssi::TextUI; require "im_utils.pm"; use vars qw($VERSION %IRSSI); $VERSION = '2.1'; %IRSSI = ( authors => 'Tijmen "timing" Ruizendaal, Matt "f0rked" Sparks, Sébastien Delafond', contact => 'tijmen.ruizendaal@gmail.com, root@f0rked.com, seb@debian.org', name => 'im_typing_notice', description => '1. Adds an item to the status bar wich shows [typing] when someone is typing a message on the supported IM networks 2. Sending typing notices to the supported IM networks (the other way around)', license => 'GPLv2', url => 'http://symlink.me/repositories/show/minbif/scripts/irssi', changed => '2009-08-25', ); my $KEEP_TYPING_TIMEOUT = 1; my $STOP_TYPING_TIMEOUT = 7; # How often to check if we are typing, or on msn, # how long to keep the typing notice up, or check # if the other user is still typing... my %timer_tag; my %typing; my %tag; my $line; my %out_typing; my $lastkey; my $keylog_active = 1; my $command_char = Irssi::settings_get_str('cmdchars'); my $to_char = Irssi::settings_get_str("completion_char"); my @control_channels; &set_local_channels(); sub set_local_channels { @control_channels = @{&ImUtils::get_channels()}; } sub event_ctcp_msg { my ($server, $msg, $from, $address) = @_; #print("CTCP: $server, $msg, $from, $address"); #print "CTCP: $msg $from $address"; return unless ImUtils::is_server_im($server, \@control_channels); if (my($type) = $msg =~ "TYPING ([0-9])") { Irssi::signal_stop(); if ($type == 0) { unset_typing($from); } elsif ($type == 1) { $typing{$from}=1; if ($address !~ /\@login\.oscar\.aol\.com/ and $address !~ /\@YAHOO/ and $address !~ /\@login\.icq\.com/) { Irssi::timeout_remove($tag{$from}); $tag{$from}=Irssi::timeout_add_once($STOP_TYPING_TIMEOUT*1000,"unset_typing",$from); } redraw($from); } elsif ($type == 2) { stale_typing($from); } } } sub unset_typing { my($from,$no_redraw)=@_; delete $typing{$from} if $typing{$from}; Irssi::timeout_remove($tag{$from}); redraw($from) if !$no_redraw; } sub stale_typing { my($from)=@_; $typing{$from}=2; redraw($from); } sub redraw { my($from)=@_; my $window = Irssi::active_win(); my $channel = $window->get_active_name(); if ($from eq $channel || ImUtils::is_channel_im($channel, \@control_channels) || $channel =~ /&chat_0/ || Irssi::settings_get_bool("bitlbee_typing_allwin")) { Irssi::statusbar_items_redraw('typing_notice'); } } sub event_msg { my ($server,$data,$from,$address,$target) = @_; return unless ImUtils::is_server_im($server, \@control_channels); my $channel=Irssi::active_win()->get_active_name(); unset_typing $from, "no redraw"; unset_typing $channel; } sub event_quit { my($server,$nick,$address,$reason)=@_; return unless ImUtils::is_server_im($server, \@control_channels); unset_typing $nick; } sub typing_notice { my ($item, $get_size_only) = @_; my $window = Irssi::active_win(); my $channel = $window->get_active_name(); if (exists($typing{$channel})) { my $append=$typing{$channel}==2 ? " (stale)" : ""; $item->default_handler($get_size_only, "{sb typing$append}", 0, 1); } else { $item->default_handler($get_size_only, "", 0, 1); Irssi::timeout_remove($tag{$channel}); } if (ImUtils::is_channel_im($channel, \@control_channels) || $channel =~ /&chat_0/ || Irssi::settings_get_bool("bitlbee_typing_allwin")) { foreach my $key (keys(%typing)) { $line .= " ".$key; if ($typing{$key}==2) { $line .= " (stale)"; } } if ($line ne "") { $item->default_handler($get_size_only, "{sb typing:$line}", 0, 1); $line = ""; } } } sub empty { my $from = shift; delete($typing{$from}); Irssi::statusbar_items_redraw('typing_notice'); } sub window_change { Irssi::statusbar_items_redraw('typing_notice'); my $win = Irssi::active_win() ? Irssi::active_win()->{active} : undef; if (ref $win && ImUtils::is_server_im($win->{server}, \@control_channels)) { if (!$keylog_active) { $keylog_active = 1; Irssi::signal_add_last('gui key pressed', 'key_pressed'); #print "Keylog started"; } } else { if ($keylog_active) { $keylog_active = 0; Irssi::signal_remove('gui key pressed', 'key_pressed'); #print "Keylog stopped"; } } } sub key_pressed { return unless (Irssi::settings_get_bool("bitlbee_send_typing") || Irssi::settings_get_bool("typing_notice")); my $key = shift; if ($key != 9 && $key != 10 && $lastkey != 27 && $key != 27 && $lastkey != 91 && $key != 126 && $key != 127) { my $server = Irssi::active_server(); my $window = Irssi::active_win(); my $nick = $window->get_active_name(); if (ImUtils::is_server_im($server, \@control_channels) && $nick ne "(status)" && $nick ne "root") { if (grep {$_ eq $nick} &ImUtils::get_all_im_nicks(\@control_channels)) { my $input = Irssi::parse_special("\$L"); my ($first_word) = split(/ /,$input); if ($input !~ /^$command_char.*/ && $first_word =~ s/$to_char$//){ send_typing($first_word); } } else { my $input = Irssi::parse_special("\$L"); if ($input !~ /^$command_char.*/ && length($input) > 0){ send_typing($nick); } } } } $lastkey = $key; } sub out_empty { my ($a) = @_; my($nick,$tag)=@{$a}; delete($out_typing{$nick}); #print $winnum."|".$nick; if (my $server=Irssi::server_find_tag($tag)) { $server->command("^CTCP $nick TYPING 0"); } } sub send_typing { my $nick = shift; if (!exists($out_typing{$nick}) || time - $out_typing{$nick} > $KEEP_TYPING_TIMEOUT) { #print "send_typing ". $nick; return unless grep { $_->{nick} eq $nick } @{&ImUtils::get_all_im_nicks(\@control_channels)}; #print "Send typing"; my $server = Irssi::active_server(); $server->command("^CTCP $nick TYPING 1"); $out_typing{$nick} = time; ### Reset 'stop-typing' timer if ($timer_tag{$nick}) { Irssi::timeout_remove($timer_tag{$nick}); delete($timer_tag{$nick}); } $timer_tag{$nick} = Irssi::timeout_add_once($STOP_TYPING_TIMEOUT*1000, 'out_empty', ["$nick", $server->{tag}]); } } #README: Delete the old bitlbee_send_typing string from ~/.irssi/config. A boolean is better. Irssi::settings_add_bool("bitlbee","bitlbee_send_typing",1); Irssi::settings_add_bool("bitlbee","bitlbee_typing_allwin",0); Irssi::settings_add_bool("bitlbee","typing_notice",0); Irssi::signal_add("ctcp msg", "event_ctcp_msg"); Irssi::signal_add("message private", "event_msg"); Irssi::signal_add("message public", "event_msg"); Irssi::signal_add("message quit", "event_quit"); Irssi::signal_add_last('window changed', 'window_change'); Irssi::signal_add_last('gui key pressed', 'key_pressed'); Irssi::statusbar_item_register('typing_notice', undef, 'typing_notice'); Irssi::statusbars_recreate_items(); Irssi::signal_add_last('channel sync', "set_local_channels"); minbif-1.0.5+git20120508/scripts/irssi/minbif_rename_gplus.pl0000644000175000017500000001150511754151773021665 0ustar sebseb#!/usr/bin/perl use strict; use Irssi; use Irssi::Irc; use vars qw( $VERSION %IRSSI ); ($VERSION) = '$Revision: 0.04 $' =~ m{ (\d+[.]\d+) }; %IRSSI = ( name => 'Minbif GPlus Renamer', authors => 'Justin Wheeler, Matias Russitto', contact => 'minbif@datademons.com', license => 'GPL', description => q{Program that will try and rename people on your GPlus } . q{from jabber's default u to their real name.} ); my $minbif_channel = '&minbif'; my $gplus_server = qr/public[.]talk[.]google[.]com/i; my $unnamed_pattern = qr/^[u_][a-z0-9]+/; my %minbif_servers; my %changed_nicks; my %attempted_to_rename; sub message_join { my ($server, $channel, $nick, $address) = @_; return if $channel ne $minbif_channel || $address !~ $gplus_server || $nick !~ $unnamed_pattern; $minbif_servers{ $server->{ tag } } = 1; $attempted_to_rename{ $nick } = q{}; $server->redirect_event( 'whois', 1, ":$nick", -1, undef, { 'event 311' => 'redir whois_data', 'event 312' => 'redir ignore_it', 'event 319' => 'redir ignore_it', 'event 320' => 'redir extended_data', }, ); $server->send_raw("WHOIS $nick $nick"); return; } sub whois_data { my ($server, $data) = @_; my ($me, $nick, $user, $host, $star, $real_name) = split /\s+/, $data, 6; return if !exists $minbif_servers{ $server->{ tag } } || $nick !~ $unnamed_pattern || !exists $attempted_to_rename{ $nick }; if (my ($actual_name) = $real_name =~ m{:(.+)(?=\s+\[)}) { if ($actual_name !~ $unnamed_pattern && $actual_name ne q{}) { _change_nickname( $server, $nick, $actual_name ); } } Irssi::signal_stop(); return; } sub _change_nickname { my ($server, $old_nick, $new_nick) = @_; $new_nick = _clean_nick( $new_nick ); foreach my $changed_nick ( keys %changed_nicks ) { delete $changed_nicks{ $changed_nick } if (time - $changed_nicks{ $changed_nick }) > 10; delete $changed_nicks{ $changed_nick } if $attempted_to_rename{ $changed_nick } ne $new_nick; } return if exists $changed_nicks{ $old_nick }; $changed_nicks{ $old_nick } = time; $attempted_to_rename{ $old_nick } = $new_nick; Irssi::print("Renaming $old_nick to $new_nick"); $server->command("quote SVSNICK $old_nick $new_nick"); return; } sub nick_used { my ($server, $data) = @_; return if !exists $minbif_servers{ $server->{ tag } }; my ($nick, $new_nick, $used_message) = split /\s+/, $data, 3; my ( $old_nick ) = grep { $attempted_to_rename{ $_ } eq $new_nick } keys %attempted_to_rename; return if !$old_nick; if ($new_nick eq substr( "${old_nick}_GPlus", 0, 29 )) { Irssi::print( qq{I tried renaming $old_nick to $new_nick with and without a } . q{gplus suffix, but was unsuccessful. You'll need to rename } . q{this user manually.} ); return; } Irssi::print("$new_nick appears to be used -- trying ${new_nick}_GPlus"); _change_nickname( $server, $old_nick, "${new_nick}_GPlus" ); Irssi::signal_stop(); return; } sub extended_data { my ($server, $data) = @_; return if !exists $minbif_servers{ $server->{ tag } }; my ($me, $nick, $rest) = split /\s+/, $data, 3; return if $nick !~ $unnamed_pattern; if ($rest =~ s/:Full\s+Name:\s+//) { return if !$rest; _change_nickname( $server, $nick, $rest ); } return; } sub _clean_nick { my ($name) = @_; $name =~ s/[^\w\d_-]+/_/g; $name =~ s/_{2,}/_/g; # Minbif's nick length limit. return substr( $name, 0, 29 ); } sub nick_change { my ($server, $new_nick, $old_nick, $host) = @_; return if !exists $minbif_servers{ $server->{ tag } }; delete $attempted_to_rename{ $old_nick }; return; } sub ignore_it { my ($server, $info) = @_; return if !exists $minbif_servers{ $server->{ tag } }; my ($me, $them, $everything_else) = split /\s+/, $info, 3; # If we just WHOIS'd them, but haven't tried renaming yet. if (exists $attempted_to_rename{ $them }) { Irssi::signal_stop(); } return; } Irssi::signal_add_first('message join' => 'message_join' ); Irssi::signal_add_first('message nick' => 'nick_change' ); Irssi::signal_add_first('whois_data' => 'whois_data' ); Irssi::signal_add_first('extended_data' => 'extended_data'); Irssi::signal_add_first('event 433' => 'nick_used' ); Irssi::signal_add_first('event 311' => 'whois_data' ); Irssi::signal_add_first('event 312' => 'ignore_it' ); Irssi::signal_add_first('event 319' => 'ignore_it' ); Irssi::signal_add_first('event 320' => 'extended_data'); minbif-1.0.5+git20120508/scripts/irssi/im_join_notice.pl0000644000175000017500000000443211754151773020646 0ustar sebseb#CHANGELOG: # # 25-08-2009 (2.1) # * externalize some utilities in im_utils.pm # #17-08-2009: #modified so it works with minbif too # #28-11-2004: #it gives a join message in a query if a user joins &bitlbee and you hve a query open with that person. # #/statusbar window add join_notice use strict; use Irssi::TextUI; require "im_utils.pm"; use vars qw($VERSION %IRSSI); $VERSION = '2.1'; %IRSSI = ( authors => 'Tijmen "timing" Ruizendaal, Sébastien Delafond', contact => 'tijmen.ruizendaal@gmail.com, seb@debian.org', name => 'IM_join_notice', description => ' 1. Adds an item to the status bar wich shows [joined: ] when someone is joining on IM. 2. Shows join messages in the query.', license => 'GPLv2', url => 'http://symlink.me/repositories/show/minbif/scripts/irssi', changed => '2009-08-25', ); my %online; my %tag; my @control_channels; &set_local_channels(); sub set_local_channels { @control_channels = @{&ImUtils::get_channels()}; } sub event_join { my ($server, $channel, $nick, $address) = @_; # print("Looking at join for: $server, $channel, $nick, $address"); if (ImUtils::is_im($server, $channel, \@control_channels)) { $online{$nick} = 1; Irssi::timeout_remove($tag{$nick}); $tag{$nick} = Irssi::timeout_add(7000, 'empty', $nick); Irssi::statusbar_items_redraw('join_notice'); my $window = Irssi::window_find_item($nick); if($window){ $window->printformat(MSGLEVEL_JOINS, 'join', $nick, $address, $channel); } } } sub join_notice { my ($item, $get_size_only) = @_; my $line; foreach my $key (keys(%online)) { $line = $line." ".$key; } if ($line ne "") { $item->default_handler($get_size_only, "{sb joined:$line}", undef, 1); $line = ""; } else { $item->default_handler($get_size_only, "", undef, 1); } } sub empty{ my $nick = shift; delete($online{$nick}); Irssi::timeout_remove($tag{$nick}); Irssi::statusbar_items_redraw('join_notice'); } Irssi::signal_add("event join", "event_join"); Irssi::signal_add_last('channel sync', "set_local_channels"); Irssi::statusbar_item_register('join_notice', undef, 'join_notice'); Irssi::statusbars_recreate_items(); Irssi::theme_register([ 'join', '{channick_hilight $0} {chanhost $1} has joined {channel $2}', ]); minbif-1.0.5+git20120508/scripts/irssi/im_utils.pm0000644000175000017500000000257711754151773017517 0ustar sebsebpackage ImUtils; use strict; sub is_server_im { my ($server, $control_channels) = @_; my @control_channels = @{$control_channels}; my $server_tag = $server->{tag}; return (grep {@{$_}[1] eq $server_tag} @control_channels); } sub is_channel_im { my ($channel, $control_channels) = @_; my @control_channels = @{$control_channels}; return (grep {@{$_}[0] eq $channel} @control_channels); } sub is_im { my ($server, $channel, $control_channels) = @_; return (is_server_im($server, $control_channels) && is_channel_im($channel, $control_channels)); } sub get_channels { my @control_channels = (); foreach my $channel(&Irssi::channels()) { my $name = $channel->{name}; my $topic = $channel->{topic}; my $type = $channel->{server}->{tag}; # print("looking at: $name, $type"); if (($name =~ m/^&/) && (($topic eq "Welcome to the control channel. Type \x02help\x02 for help information.") || ($type eq "minbif"))) { # print("found $name, $type"); push(@control_channels, [$name, $type]); } } return \@control_channels; } sub get_all_im_nicks { my ($control_channels) = @_; my @control_channels = @{$control_channels}; my @nicks = (); foreach my $list (@control_channels) { my ($channel, $server_tag) = @{$list}; push(@nicks, Irssi::server_find_tag($server_tag)->channel_find($channel)->nicks()); } return \@nicks; } 1; minbif-1.0.5+git20120508/scripts/irssi/minbif_rename_facebook.pl0000644000175000017500000001147711754151773022314 0ustar sebseb#!/usr/bin/perl use strict; use Irssi; use Irssi::Irc; use vars qw( $VERSION %IRSSI ); ($VERSION) = '$Revision: 0.04 $' =~ m{ (\d+[.]\d+) }; %IRSSI = ( name => 'Minbif Facebook Renamer', authors => 'Justin Wheeler', contact => 'minbif@datademons.com', license => 'GPL', description => q{Program that will try and rename people on your Facebook } . q{from jabber's default u to their real name.} ); my $minbif_channel = '&minbif'; my $facebook_server = qr/chat[.]facebook[.]com/i; my $unnamed_pattern = qr/^[u-]\d+/; my %minbif_servers; my %changed_nicks; my %attempted_to_rename; sub message_join { my ($server, $channel, $nick, $address) = @_; return if $channel ne $minbif_channel || $address !~ $facebook_server || $nick !~ $unnamed_pattern; $minbif_servers{ $server->{ tag } } = 1; $attempted_to_rename{ $nick } = q{}; $server->redirect_event( 'whois', 1, ":$nick", -1, undef, { 'event 311' => 'redir whois_data', 'event 312' => 'redir ignore_it', 'event 319' => 'redir ignore_it', 'event 320' => 'redir extended_data', }, ); $server->send_raw("WHOIS $nick $nick"); return; } sub whois_data { my ($server, $data) = @_; my ($me, $nick, $user, $host, $star, $real_name) = split /\s+/, $data, 6; return if !exists $minbif_servers{ $server->{ tag } } || $nick !~ $unnamed_pattern || !exists $attempted_to_rename{ $nick }; if (my ($actual_name) = $real_name =~ m{:(.+)(?=\s+\[)}) { if ($actual_name !~ $unnamed_pattern && $actual_name ne q{}) { _change_nickname( $server, $nick, $actual_name ); } } Irssi::signal_stop(); return; } sub _change_nickname { my ($server, $old_nick, $new_nick) = @_; $new_nick = _clean_nick( $new_nick ); foreach my $changed_nick ( keys %changed_nicks ) { delete $changed_nicks{ $changed_nick } if (time - $changed_nicks{ $changed_nick }) > 10; delete $changed_nicks{ $changed_nick } if $attempted_to_rename{ $changed_nick } ne $new_nick; } return if exists $changed_nicks{ $old_nick }; $changed_nicks{ $old_nick } = time; $attempted_to_rename{ $old_nick } = $new_nick; Irssi::print("Renaming $old_nick to $new_nick"); $server->command("quote SVSNICK $old_nick $new_nick"); return; } sub nick_used { my ($server, $data) = @_; return if !exists $minbif_servers{ $server->{ tag } }; my ($nick, $new_nick, $used_message) = split /\s+/, $data, 3; my ( $old_nick ) = grep { $attempted_to_rename{ $_ } eq $new_nick } keys %attempted_to_rename; return if !$old_nick; if ($new_nick eq substr( "${old_nick}_Facebook", 0, 29 )) { Irssi::print( qq{I tried renaming $old_nick to $new_nick with and without a } . q{facebook suffix, but was unsuccessful. You'll need to rename } . q{this user manually.} ); return; } Irssi::print("$new_nick appears to be used -- trying ${new_nick}_Facebook"); _change_nickname( $server, $old_nick, "${new_nick}_Facebook" ); Irssi::signal_stop(); return; } sub extended_data { my ($server, $data) = @_; return if !exists $minbif_servers{ $server->{ tag } }; my ($me, $nick, $rest) = split /\s+/, $data, 3; return if $nick !~ $unnamed_pattern; if ($rest =~ s/:Full\s+Name:\s+//) { return if !$rest; _change_nickname( $server, $nick, $rest ); } return; } sub _clean_nick { my ($name) = @_; $name =~ s/[^\w\d_-]+/_/g; $name =~ s/_{2,}/_/g; # Minbif's nick length limit. return substr( $name, 0, 29 ); } sub nick_change { my ($server, $new_nick, $old_nick, $host) = @_; return if !exists $minbif_servers{ $server->{ tag } }; delete $attempted_to_rename{ $old_nick }; return; } sub ignore_it { my ($server, $info) = @_; return if !exists $minbif_servers{ $server->{ tag } }; my ($me, $them, $everything_else) = split /\s+/, $info, 3; # If we just WHOIS'd them, but haven't tried renaming yet. if (exists $attempted_to_rename{ $them }) { Irssi::signal_stop(); } return; } Irssi::signal_add_first('message join' => 'message_join' ); Irssi::signal_add_first('message nick' => 'nick_change' ); Irssi::signal_add_first('whois_data' => 'whois_data' ); Irssi::signal_add_first('extended_data' => 'extended_data'); Irssi::signal_add_first('event 433' => 'nick_used' ); Irssi::signal_add_first('event 311' => 'whois_data' ); Irssi::signal_add_first('event 312' => 'ignore_it' ); Irssi::signal_add_first('event 319' => 'ignore_it' ); Irssi::signal_add_first('event 320' => 'extended_data'); minbif-1.0.5+git20120508/scripts/irssi/im_cacacam.pl0000644000175000017500000001103211754151773017710 0ustar sebsebuse Irssi; use strict; use IO::Handle; # for (auto)flush use Fcntl; # for sysopen use vars qw($VERSION %IRSSI); $VERSION = '0.1'; %IRSSI = ( authors => 'Romain Bignon', contact => 'romain@peerfuse.org', name => 'im_cacacam', description => 'draw cacacam', license => 'GPLv2', url => 'http://minbif.im', changed => '2009-10-03', ); my $caca_width = 40; my $screen_resizing = 0; my @nicklist=(); my ($height, $irssi_width); sub cmd_screen_start { if (!defined($ENV{'STY'})) { Irssi::print 'screen not detected, screen mode only works inside screen'; return; } #$mode = $SCREEN; #Irssi::signal_add_last('gui print text finished', \&sig_gui_print_text_finished); #Irssi::signal_add_last('gui page scrolled', \&sig_page_scrolled); Irssi::signal_add('terminal resized', \&sig_terminal_resized); screen_size(); draw_cacacam(); } sub screen_stop { #$mode = $OFF; #Irssi::signal_remove('gui print text finished', \&sig_gui_print_text_finished); #Irssi::signal_remove('gui page scrolled', \&sig_page_scrolled); Irssi::signal_remove('terminal resized', \&sig_terminal_resized); system 'screen -x '.$ENV{'STY'}.' -X fit'; } sub screen_size { $screen_resizing = 1; # fit screen system 'screen -x '.$ENV{'STY'}.' -X fit'; # get size (from perldoc -q size) my ($winsize, $row, $col, $xpixel, $ypixel); eval 'use Term::ReadKey; ($col, $row, $xpixel, $ypixel) = GetTerminalSize'; # require Term::ReadKey 'GetTerminalSize'; # ($col, $row, $xpixel, $ypixel) = Term::ReadKey::GetTerminalSize; #}; if ($@) { # no Term::ReadKey, try the ugly way eval { require 'sys/ioctl.ph'; # without this reloading doesn't work. workaround for some unknown bug do 'asm/ioctls.ph'; }; # ugly way not working, let's try something uglier, the dg-hack(tm) (constant for linux only?) if($@) { no strict 'refs'; *TIOCGWINSZ = sub { return 0x5413 } } unless (defined &TIOCGWINSZ) { die "Term::ReadKey not found, and ioctl 'workaround' failed. Install the Term::ReadKey perl module to use screen mode.\n"; } open(TTY, "+get_active_name()); if($i < 0) { return; } my $n = @nicklist[$i]; cacacam_write_start(); my $line = 1; for (my $i=0;$line < $height && $i < $n->{'buffer'}; $i++) { cacacam_write_line($line++, $n->{'buffer'}[$i]); } cacacam_write_end(); } sub find_nick { my ($nick) = @_; for (my $i=0;$i < @nicklist; $i++) { if ($nicklist[$i]->{'nick'} eq $nick) { return $i; } } return -1; } # insert nick(as hash) into nicklist # pre: cmp has to be calculated sub insert_nick { my ($nick) = @_; push @nicklist, $nick; } # remove nick(as nr) from nicklist sub remove_nick { my ($nr) = @_; splice @nicklist, $nr, 1; } sub dccinfoline { my ($server, $data) = @_; my $nick; my $i = find_nick($server->{nick}); if($i < 0) { $nick = {'nick' => $server->{nick}, 'i' => 0, 'buffer' => ()}; insert_nick($nick); } else { $nick = @nicklist[$i]; } $nick->{'buffer'}[$nick->{'i'}++] = $data; if($nick->{'i'} >= 20) { $nick->{'i'} = 0; draw_cacacam(); } } Irssi::signal_add_last('window item changed', \&draw_cacacam); Irssi::signal_add_last('window changed', \&draw_cacacam); Irssi::signal_add("dcc chat message","dccinfoline"); cmd_screen_start(); minbif-1.0.5+git20120508/minbif.motd0000644000175000017500000000013611754151773014632 0ustar sebsebHey dear, this is the MinBif IRC instant messaging gateway! Enjoy. Website: http://minbif.im minbif-1.0.5+git20120508/src/0000755000175000017500000000000011754151773013270 5ustar sebsebminbif-1.0.5+git20120508/src/im/0000755000175000017500000000000011754151773013675 5ustar sebsebminbif-1.0.5+git20120508/src/im/request.h0000644000175000017500000001216211754151773015540 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_REQUEST_H #define IM_REQUEST_H #include #include #include #include #include #include "account.h" namespace im { using std::map; using std::vector; using std::string; class RequestFieldNotFound : public std::exception {}; class RequestNick; class RequestField { public: virtual ~RequestField() {} virtual void setLabel(const string& label) = 0; virtual string getLabel() const = 0; virtual string getText() const = 0; virtual void runCallback() = 0; }; class Request { protected: Account account; string title; string primary; string secondary; PurpleRequestType type; static PurpleNotifyUiOps notify_ops; static void* notify_message(PurpleNotifyMsgType type, const char *title, const char *primary, const char *secondary); static void* notify_formatted(const char *title, const char *primary, const char *secondary, const char *text); static void* notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info); static PurpleRequestUiOps uiops; static RequestNick* nick; static Request* addRequest(Request* request); static void closeRequest(const Request* request); static void * request_input(const char *title, const char *primary, const char *secondary, const char *default_value, gboolean multiline, gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data); static void* request_action(const char *title, const char *primary, const char *secondary, int default_value, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t actioncount, va_list actions); static void * request_choice(const char *title, const char *primary, const char *secondary, int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, va_list choices); static void* request_fields(const char *title, const char *primary, const char *secondary, PurpleRequestFields *allfields, const char *ok, GCallback ok_cb, const char *cancel, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *userdata); static void* request_file(const char *title, const char *filename, gboolean savedialog, GCallback ok_cb, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data); static void request_close(PurpleRequestType type, void *ui_handle); public: static vector requests; static void init(); static void uninit(); static Request* getFirstRequest(); static void closeFirstRequest(); Request(PurpleRequestType type, const Account& account, const string& title, const string& primary, const string& secondary); virtual ~Request() {} virtual void process(const string& answer) const = 0; virtual void display() const = 0; virtual void close(); }; class RequestInput : public Request { string default_value; PurpleRequestInputCb callback; void* user_data; public: RequestInput(PurpleRequestType type, const Account& account, const string& title, const string& primary, const string& secondary, const string& _default_value, PurpleRequestInputCb _callback, void* _user_data) : Request(type, account, title, primary, secondary), default_value(_default_value), callback(_callback), user_data(_user_data) {} virtual void process(const string& answer) const; virtual void display() const; }; class RequestFieldList : public Request { map fields; vector subrequests; public: RequestFieldList(PurpleRequestType type, const Account& account, const string& title, const string& primary, const string& secondary) : Request(type, account, title, primary, secondary) {} ~RequestFieldList(); void addField(RequestField* field); RequestField* getField(const string& label) const; void addSubrequest(Request* request); virtual void process(const string& answer) const; virtual void display() const; virtual void close(); }; }; /* namespace im */ #endif /* IM_REQUEST_H */ minbif-1.0.5+git20120508/src/im/account.cpp0000644000175000017500000010105511754151773016037 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #ifdef HAVE_IMLIB #include #endif /* HAVE_IMLIB */ #include "im/im.h" #include "im/account.h" #include "im/conversation.h" #include "im/buddy.h" #include "im/purple.h" #include "core/log.h" #include "core/version.h" #include "irc/irc.h" #include "irc/status_channel.h" #include "irc/user.h" #include "irc/buddy.h" namespace im { Account::Account() : account(NULL) {} Account::Account(PurpleAccount* _account, Protocol _proto) : account(_account), proto(_proto) { if(account && !proto.isValid()) { if(account && account->gc) proto = Protocol(account->gc->prpl); else proto = Purple::getProtocolByPurpleID(account->protocol_id); } } bool Account::operator==(const Account& account) const { return this->isValid() && account.isValid() && account.account == this->account; } bool Account::operator!=(const Account& account) const { return !isValid() || !account.isValid() || account.account != this->account; } string Account::getUsername() const { assert(isValid()); return account->username; } string Account::getPassword() const { assert(isValid()); return p2s(purple_account_get_password(account)); } void Account::setPassword(string password) { assert(isValid()); purple_account_set_password(account, password.c_str()); } void Account::registerAccount() const { assert(isValid()); purple_account_register(account); } vector Account::getCommandsList() const { assert(isValid()); GList *actions, *l; vector cmds; PurpleConnection *gc = purple_account_get_connection(account); PurplePlugin *plugin = (gc && PURPLE_CONNECTION_IS_CONNECTED(gc)) ? gc->prpl : NULL; if(!plugin || !PURPLE_PLUGIN_HAS_ACTIONS(plugin)) return cmds; actions = PURPLE_PLUGIN_ACTIONS(plugin, gc); for (l = actions; l != NULL; l = l->next) if (l->data) cmds.push_back(irc::Nick::nickize(((PurplePluginAction *) l->data)->label)); return cmds; } bool Account::callCommand(const string& cmd) const { assert(isValid()); GList *actions, *l; PurpleConnection *gc = purple_account_get_connection(account); PurplePlugin *plugin = (gc && PURPLE_CONNECTION_IS_CONNECTED(gc)) ? gc->prpl : NULL; if(!plugin || !PURPLE_PLUGIN_HAS_ACTIONS(plugin)) return false; actions = PURPLE_PLUGIN_ACTIONS(plugin, gc); for (l = actions; l != NULL; l = l->next) { PurplePluginAction* action = (PurplePluginAction*)l->data; if (l->data && irc::Nick::nickize(action->label) == cmd) { action->plugin = plugin; action->context = gc; action->callback(action); return true; } } return false; } Protocol::Options Account::getOptions() const { assert(isValid()); Protocol::Options options = getProtocol().getOptions(); FOREACH(Protocol::Options, options, it) { Protocol::Option& option = it->second; switch(option.getType()) { case Protocol::Option::STR: option.setValue(purple_account_get_string(account, option.getName().c_str(), option.getValue().c_str())); break; case Protocol::Option::INT: option.setValue(t2s(purple_account_get_int(account, option.getName().c_str(), option.getValueInt()))); break; case Protocol::Option::BOOL: option.setValue(purple_account_get_bool(account, option.getName().c_str(), option.getValueBool()) ? "true" : "false"); break; case Protocol::Option::ACCID: option.setValue(getID()); break; case Protocol::Option::STATUS_CHANNEL: option.setValue(getStatusChannelName()); break; case Protocol::Option::PASSWORD: option.setValue(getPassword()); break; case Protocol::Option::SERVER_ALIASES: option.setValue(hasServerAliases() ? "true" : "false"); break; case Protocol::Option::NONE: /* not supported. */ break; } } return options; } void Account::setOptions(const Protocol::Options& options) { assert(isValid()); for(Protocol::Options::const_iterator it = options.begin(); it != options.end(); ++it) { const Protocol::Option& option = it->second; switch(option.getType()) { case Protocol::Option::STR: purple_account_set_string(account, option.getName().c_str(), option.getValue().c_str()); break; case Protocol::Option::INT: purple_account_set_int(account, option.getName().c_str(), option.getValueInt()); break; case Protocol::Option::BOOL: purple_account_set_bool(account, option.getName().c_str(), option.getValueBool()); break; case Protocol::Option::ACCID: { string value = option.getValue(); if(getID() == value) break; if(isConnected()) throw Protocol::OptionError("Can't change ID of a connected account"); if(value.empty()) value = Purple::getNewAccountName(proto, *this); else if(Purple::getIM()->getAccount(value).isValid() == true) throw Protocol::OptionError("This ID is already used."); setID(value); break; } case Protocol::Option::STATUS_CHANNEL: if(getStatusChannelName() == option.getValue()) break; if(!irc::Channel::isChanName(option.getValue()) || !irc::Channel::isStatusChannel(option.getValue())) throw Protocol::OptionError("'" + option.getValue() + "' is not a valid channel name"); setStatusChannelName(option.getValue()); break; case Protocol::Option::PASSWORD: if (option.getValue() == getPassword()) break; if (option.getValue().empty()) purple_account_set_remember_password(account, FALSE); else { setPassword(option.getValue()); purple_account_set_remember_password(account, TRUE); } break; case Protocol::Option::SERVER_ALIASES: setServerAliases(option.getValueBool()); break; case Protocol::Option::NONE: /* Not happy. */ break; } } } void Account::setBuddyIcon(string filename) { assert(isValid()); PurplePlugin *plug = purple_find_prpl(purple_account_get_protocol_id(account)); if (plug) { PurplePluginProtocolInfo *prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plug); if (prplinfo != NULL && //purple_account_get_bool(account, "use-global-buddyicon", TRUE) && prplinfo->icon_spec.format) { GError* temp_error = NULL; #ifdef HAVE_IMLIB Imlib_Image img = imlib_load_image(filename.c_str()); char* temp_filename = NULL; if (img) { int temp_fd; /* Create a stupid tmp file, write it, close it. Save image as png in it. Fuck it. */ temp_fd = g_file_open_tmp("minbif_new_icon_XXXXXX", &temp_filename, &temp_error); if (temp_error) { b_log[W_ERR] << "Unable to create a temporary file: " << temp_error->message; g_error_free(temp_error); temp_filename = NULL; } else { char** prpl_formats = g_strsplit(prplinfo->icon_spec.format,",",0); ImlibLoadError err = IMLIB_LOAD_ERROR_UNKNOWN; close(temp_fd); /* Try to encode in a supported format. */ imlib_context_set_image(img); for (size_t i = 0; prpl_formats[i] && err != IMLIB_LOAD_ERROR_NONE; ++i) { imlib_image_set_format(prpl_formats[i]); imlib_save_image_with_error_return(temp_filename, &err); } if (err != IMLIB_LOAD_ERROR_NONE) b_log[W_ERR] << "Unable to encode image for " << getID(); else filename = temp_filename; g_strfreev(prpl_formats); } imlib_free_image(); } #endif /* HAVE_IMLIB */ gchar *contents; gsize len; if (g_file_get_contents(filename.c_str(), &contents, &len, &temp_error)) { if ((prplinfo->icon_spec.max_filesize != 0) && (len > prplinfo->icon_spec.max_filesize)) { b_log[W_ERR] << "Unable to set image on " << getID() << ": image too large"; } else { purple_buddy_icons_set_account_icon(account, (guchar *)contents, len); /* Useless, and a temp filename is not safe: * purple_account_set_buddy_icon_path(account, temp_filename); */ } } else if (temp_error) { b_log[W_ERR] << "Unable to get image content on " << getID() << ": " << temp_error->message; g_error_free(temp_error); } #ifdef HAVE_IMLIB if (temp_filename) { unlink(temp_filename); g_free(temp_filename); } #endif /* HAVE_IMLIB */ } } } void Account::setStatus(PurpleStatusPrimitive prim, string message) { assert(isValid()); const char *id; PurpleStatus* status; if (prim == PURPLE_STATUS_UNSET) { status = purple_account_get_active_status(account); id = purple_status_get_id(status); } else { id = purple_primitive_get_id_from_type(prim); status = purple_account_get_status(account, id); } if (!status) return; irc::StatusChannel* chan = getStatusChannel(); if (message.empty() && chan) message = chan->getTopic(); if (message.empty() && prim != PURPLE_STATUS_UNSET) { /* Get the status' message only if we change it. */ message = p2s(purple_status_get_attr_string(status, "message")); if (!message.empty() && chan && purple_primitive_get_type_from_id(id) == PURPLE_STATUS_AVAILABLE) chan->setTopic(NULL, message); } char *utf8 = purple_utf8_try_convert(message.c_str()); purple_account_set_status(account, id, TRUE, "message", utf8, (char*)NULL); g_free(utf8); } PurpleStatusPrimitive Account::getStatus() const { assert(isValid()); PurpleStatus *s = purple_account_get_active_status(account); if (!s) return PURPLE_STATUS_UNSET; return purple_primitive_get_type_from_id(purple_status_get_id(s)); } string Account::getStatusMessage(PurpleStatusPrimitive prim) const { assert(isValid()); PurpleStatus *s; if (prim == PURPLE_STATUS_UNSET) s = purple_account_get_active_status(account); else s = purple_account_get_status(account, purple_primitive_get_id_from_type(prim)); if (!s) return ""; return p2s(purple_status_get_attr_string(s, "message")); } void Account::setID(string id) const { assert(isValid()); purple_account_set_ui_string(account, MINBIF_VERSION_NAME, "id", id.c_str()); } string Account::getID(bool calculate_newone) const { assert(isValid()); string n = purple_account_get_ui_string(account, MINBIF_VERSION_NAME, "id", ""); if(calculate_newone && n.empty()) { n = Purple::getNewAccountName(proto, *this); setID(n); } return n; } bool Account::hasServerAliases() const { assert(isValid()); return purple_account_get_ui_bool(account, MINBIF_VERSION_NAME, "server_aliases", true); } void Account::setServerAliases(bool b) { assert(isValid()); purple_account_set_ui_bool(account, MINBIF_VERSION_NAME, "server_aliases", b); } string Account::getStatusChannelName() const { assert(isValid()); string n = purple_account_get_ui_string(account, MINBIF_VERSION_NAME, "channel", ""); return n; } void Account::setStatusChannelName(const string& c) { assert(isValid()); leaveStatusChannel(); purple_account_set_ui_string(account, MINBIF_VERSION_NAME, "channel", c.c_str()); createStatusChannel(); } void Account::enqueueChannelJoin(const string& c) { assert(isValid()); string list = purple_account_get_ui_string(account, MINBIF_VERSION_NAME, "join_queue", ""); if(!list.empty()) list += ","; list += c; purple_account_set_ui_string(account, MINBIF_VERSION_NAME, "join_queue", list.c_str()); } void Account::flushChannelJoins() { assert(isValid()); string list = purple_account_get_ui_string(account, MINBIF_VERSION_NAME, "join_queue", ""); string cname; while((cname = stringtok(list, ",")).empty() == false) this->joinChat(cname, ""); purple_account_set_ui_string(account, MINBIF_VERSION_NAME, "join_queue", ""); } void Account::abortChannelJoins() { assert(isValid()); if (isConnected()) { irc::IRC* irc = Purple::getIM()->getIRC(); string list = purple_account_get_ui_string(account, MINBIF_VERSION_NAME, "join_queue", ""); string cname; while((cname = stringtok(list, ",")).empty() == false) irc->getUser()->send(irc::Message(ERR_NOSUCHCHANNEL).setSender(irc) .setReceiver(irc->getUser()) .addArg("#" + cname + ":" + getID()) .addArg("No such channel")); } purple_account_set_ui_string(account, MINBIF_VERSION_NAME, "join_queue", ""); } string Account::getServername() const { assert(isValid()); return getUsername() + ":" + getID(); } PurpleConnection* Account::getPurpleConnection() const { assert(isValid()); return purple_account_get_connection(account); } bool Account::isConnected() const { assert(isValid()); return purple_account_is_connected(account); } bool Account::isConnecting() const { assert(isValid()); return purple_account_is_connecting(account); } vector Account::getBuddies() const { assert(isValid()); vector buddies; if(!purple_get_blist()) return buddies; GSList* bl = purple_find_buddies(account, NULL); GSList* cur; for(cur = bl; cur != NULL; cur = cur->next) { if(!PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)cur->data)) continue; PurpleBuddy* b = (PurpleBuddy*)cur->data; buddies.push_back(Buddy(b)); } g_slist_free(bl); return buddies; } void Account::updatedAllBuddies() const { assert(isValid()); vector buddies = getBuddies(); for(vector::iterator it = buddies.begin(); it != buddies.end(); ++it) it->updated(); } void Account::displayRoomList() const { assert(isValid()); purple_roomlist_get_list(account->gc); } void Account::connect() const { assert(isValid()); purple_account_set_enabled(account, MINBIF_VERSION_NAME, true); } void Account::disconnect() const { assert(isValid()); removeReconnection(true); purple_account_set_enabled(account, MINBIF_VERSION_NAME, false); } int Account::delayReconnect() const { assert(isValid()); int delay = purple_account_get_ui_int(account, MINBIF_VERSION_NAME, "delay-reconnect", 15); delay *= 2; int id = purple_account_get_ui_int(account, MINBIF_VERSION_NAME, "id-reconnect", -1); if(id >= 0) g_source_remove(id); id = g_timeout_add(delay*1000, Account::reconnect, account); purple_account_set_ui_int(account, MINBIF_VERSION_NAME, "delay-reconnect", delay); purple_account_set_ui_int(account, MINBIF_VERSION_NAME, "id-reconnect", id); return delay; } void Account::removeReconnection(bool verbose) const { assert(isValid()); int id = purple_account_get_ui_int(account, MINBIF_VERSION_NAME, "id-reconnect", -1); if(id >= 0) { g_source_remove(id); if(verbose) b_log[W_INFO|W_SNO] << "Abort auto-reconnection to " << getServername(); } purple_account_set_ui_int(account, MINBIF_VERSION_NAME, "delay-reconnect", 15); purple_account_set_ui_int(account, MINBIF_VERSION_NAME, "id-reconnect", -1); } irc::StatusChannel* Account::getStatusChannel() const { irc::IRC* irc = Purple::getIM()->getIRC(); irc::StatusChannel* chan; string channame = getStatusChannelName(); if(!irc::Channel::isStatusChannel(channame)) return NULL; chan = dynamic_cast(irc->getChannel(channame)); if(!chan) { b_log[W_ERR] << "Status channel " << channame << " is not found."; return NULL; } return chan; } void Account::createStatusChannel() { assert(isValid()); irc::IRC* irc = Purple::getIM()->getIRC(); irc::StatusChannel* chan; string channame = getStatusChannelName(); if(!irc::Channel::isStatusChannel(channame)) return; chan = dynamic_cast(irc->getChannel(channame)); if(!chan) { chan = new irc::StatusChannel(irc, channame); irc->addChannel(chan); irc->getUser()->join(chan, irc::ChanUser::OP); } string status = getStatusMessage(PURPLE_STATUS_AVAILABLE); if (chan->getTopic() != status) { if (!status.empty()) chan->setTopic(NULL, status); else setStatus(PURPLE_STATUS_UNSET, chan->getTopic()); } chan->addAccount(*this); vector buddies = getBuddies(); for(vector::iterator b = buddies.begin(); b != buddies.end(); ++b) b->updated(); } void Account::leaveStatusChannel() { assert(isValid()); irc::IRC* irc = Purple::getIM()->getIRC(); irc::StatusChannel* chan = getStatusChannel(); if (!chan) return; chan->removeAccount(*this); vector buddies = getBuddies(); for(vector::iterator b = buddies.begin(); b != buddies.end(); ++b) if(b->getNick()) b->getNick()->part(chan, "Leaving status channel"); if(chan->countAccounts() == 0) irc->removeChannel(chan->getName()); } vector Account::getDenyList() const { assert(isValid()); vector list; GSList *l; for (l = account->deny; l != NULL; l = l->next) list.push_back((const char*)l->data); return list; } bool Account::deny(const string& who) const { assert(isValid()); if (account->perm_deny != PURPLE_PRIVACY_DENY_USERS) { account->perm_deny = PURPLE_PRIVACY_DENY_USERS; if (purple_account_is_connected(account)) serv_set_permit_deny(purple_account_get_connection(account)); } return purple_privacy_deny_add(account, who.c_str(), FALSE); } bool Account::allow(const string& who) const { assert(isValid()); return purple_privacy_deny_remove(account, who.c_str(), FALSE); } void Account::addBuddy(const string& username, const string& group) const { assert(isValid()); assert(username.empty() == false); assert(group.empty() == false); PurpleGroup* grp = purple_find_group(group.c_str()); if (!grp) { grp = purple_group_new(group.c_str()); purple_blist_add_group(grp, NULL); } PurpleBuddy* buddy = purple_buddy_new(account, username.c_str(), username.c_str()); purple_blist_add_buddy(buddy, NULL, grp, NULL); purple_account_add_buddy(account, buddy); } void Account::removeBuddy(Buddy buddy) const { assert(isValid()); purple_account_remove_buddy(account, buddy.getPurpleBuddy(), buddy.getPurpleGroup()); purple_blist_remove_buddy(buddy.getPurpleBuddy()); } bool Account::supportsChats() const { assert(isValid()); PurpleConnection *gc = purple_account_get_connection(account); PurplePluginProtocolInfo *prpl_info = NULL; prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); return (prpl_info->chat_info != NULL); } map Account::getChatParameters() const { assert(isValid()); assert(isConnected()); PurpleConnection* gc = purple_account_get_connection(account); map m; GList *list = NULL, *tmp; GHashTable *defaults = NULL; if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc); if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL) defaults = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, NULL); for(tmp = list; tmp; tmp = tmp->next) { struct proto_chat_entry *pce = static_cast(tmp->data); char* value = (char*)g_hash_table_lookup(defaults, pce->identifier); m[pce->identifier] = value ? value : ""; g_free(pce); } g_list_free(list); g_hash_table_destroy(defaults); return m; } bool Account::joinChat(const string& name, const string& parameters) const { assert(isValid()); if(!isConnected()) { b_log[W_SNO|W_INFO] << "Not connected"; return false; } #if 0 PurpleConnection* gc = purple_account_get_connection(account); PurpleConversation* conv; if (!(conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name.c_str(), account))) { conv = purple_conversation_new(PURPLE_CONV_TYPE_CHAT, account, name.c_str()); purple_conv_chat_left(PURPLE_CONV_CHAT(conv)); } else purple_conversation_present(conv); PurpleChat *chat; GHashTable *hash = NULL; chat = purple_blist_find_chat(account, name.c_str()); if (chat == NULL) { PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)); if (info->chat_info_defaults != NULL) hash = info->chat_info_defaults(gc, name.c_str()); } else hash = purple_chat_get_components(chat); serv_join_chat(gc, hash); if (chat == NULL && hash != NULL) g_hash_table_destroy(hash); #else /* !0 */ if(!supportsChats()) { /* Ok, so chats are not officially supported on this protocol... * But there is another way to create a group chat, for example * on MSN, where we can initiate a chat with a buddy, and invite * others people in. * * But there is only one way to create this kind of group chat, * it's to call the callback function in a buddy's menu from * protocol plugin. * * So this FUCKING HACK is looking for the menu, and find the * string "Initiate _Chat", and call his callback. * * It's yeah ugly. */ PurpleConnection *gc = purple_account_get_connection(account); GList *l, *ll; PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); if(!prpl_info || !prpl_info->blist_node_menu) return false; irc::Buddy* n = dynamic_cast(Purple::getIM()->getIRC()->getNick(name)); if(!n) return false; PurpleBlistNode* node = (PurpleBlistNode*)n->getBuddy().getPurpleBuddy(); if(!node) return false; for(l = ll = prpl_info->blist_node_menu(node); l; l = l->next) { PurpleMenuAction *act = (PurpleMenuAction *) l->data; if(!act->children && act->callback != NULL && !strcasecmp(act->label, "Initiate _Chat")) { ((void (*) (gpointer, gpointer))act->callback)(node, act->data); g_list_free(ll); return true; } } g_list_free(ll); return false; } PurpleChat *chat; GHashTable *hash = NULL; PurpleConnection *gc; PurplePluginProtocolInfo *info; gc = purple_account_get_connection(account); info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)); if (info->chat_info_defaults != NULL) hash = info->chat_info_defaults(gc, name.c_str()); /* Parse parameters */ string param, params = parameters; while((param = stringtok(params, ";")).empty() == false) { string key = stringtok(param, "="); if(key.empty()) continue; g_hash_table_replace(hash, g_strdup(key.c_str()), g_strdup(param.c_str())); } chat = purple_chat_new(account, name.c_str(), hash); if (chat != NULL) { //if ((grp = purple_find_group(group)) == NULL) { // grp = purple_group_new(group); // purple_blist_add_group(grp, NULL); //} //purple_blist_add_chat(chat, grp, NULL); //purple_blist_alias_chat(chat, alias); //purple_blist_node_set_bool((PurpleBlistNode*)chat, "gnt-autojoin", autojoin); const char *name; PurpleConversation *conv; const char *alias; /* This hack here is to work around the fact that there's no good way of * getting the actual name of a chat. I don't understand why we return * the alias for a chat when all we want is the name. */ alias = chat->alias; chat->alias = NULL; name = purple_chat_get_name(chat); conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, name, account); chat->alias = (char *)alias; if (!conv || purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) { serv_join_chat(purple_account_get_connection(account), purple_chat_get_components(chat)); } else if (conv) { purple_conversation_present(conv); } } #endif /* !0 */ return chat != NULL; } /* STATIC */ PurpleConnectionUiOps Account::conn_ops = { Account::connecting, Account::connected, Account::disconnected, NULL, /* notice */ NULL, NULL, /* network_connected */ NULL, /* network_disconnected */ Account::disconnect_reason, NULL, NULL, NULL }; PurpleAccountUiOps Account::acc_ops = { notify_added, NULL, request_add, request_authorize, request_close, NULL, NULL, NULL, NULL }; void* Account::getHandler() { static int handler; return &handler; } void Account::init() { purple_connections_set_ui_ops(&conn_ops); purple_accounts_set_ui_ops(&acc_ops); purple_signal_connect(purple_accounts_get_handle(), "account-added", getHandler(), PURPLE_CALLBACK(account_added), NULL); purple_signal_connect(purple_accounts_get_handle(), "account-removed", getHandler(), PURPLE_CALLBACK(account_removed), NULL); purple_signal_connect(purple_connections_get_handle(), "signed-on", getHandler(), G_CALLBACK(account_signed_on_cb), GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_ONLINE)); map accounts = Purple::getAccountsList(); for(map::iterator it = accounts.begin(); it != accounts.end(); ++it) it->second.createStatusChannel(); } void Account::uninit() { purple_accounts_set_ui_ops(NULL); purple_connections_set_ui_ops(NULL); purple_signals_disconnect_by_handle(getHandler()); } gboolean Account::reconnect(void* data) { Account acc((PurpleAccount*)data); purple_account_set_ui_int(acc.account, MINBIF_VERSION_NAME, "id-reconnect", -1); acc.connect(); return FALSE; } void Account::account_added(PurpleAccount* account) { } void Account::account_removed(PurpleAccount* a) { Account account(a); account.abortChannelJoins(); account.removeReconnection(); account.leaveStatusChannel(); } static char *make_info(PurpleAccount *account, PurpleConnection *gc, const char *remote_user, const char *id, const char *alias, const char *msg) { if (msg != NULL && *msg == '\0') msg = NULL; return g_strdup_printf("%s%s%s%s has made %s his or her buddy%s%s", remote_user, (alias != NULL ? " (" : ""), (alias != NULL ? alias : ""), (alias != NULL ? ")" : ""), (id != NULL ? id : (purple_connection_get_display_name(gc) != NULL ? purple_connection_get_display_name(gc) : purple_account_get_username(account))), (msg != NULL ? ": " : "."), (msg != NULL ? msg : "")); } typedef struct { PurpleAccount *account; char *username; char *alias; } AddUserData; static void free_add_user_data(AddUserData *data) { g_free(data->username); if (data->alias != NULL) g_free(data->alias); g_free(data); } static void add_user_cb(AddUserData *data) { PurpleConnection *gc = purple_account_get_connection(data->account); if (g_list_find(purple_connections_get_all(), gc)) { purple_blist_request_add_buddy(data->account, data->username, NULL, data->alias); } free_add_user_data(data); } void Account::notify_added(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *msg) { char *buffer; PurpleConnection *gc; gc = purple_account_get_connection(account); buffer = make_info(account, gc, remote_user, id, alias, msg); purple_notify_info(NULL, NULL, buffer, NULL); g_free(buffer); } void Account::request_add(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *msg) { char *buffer; PurpleConnection *gc; AddUserData *data; gc = purple_account_get_connection(account); data = g_new0(AddUserData, 1); data->account = account; data->username = g_strdup(remote_user); data->alias = (alias != NULL ? g_strdup(alias) : NULL); buffer = make_info(account, gc, remote_user, id, alias, msg); purple_request_action(NULL, NULL, "Add buddy to your list?", buffer, PURPLE_DEFAULT_ACTION_NONE, account, remote_user, NULL, data, 2, "Add", G_CALLBACK(add_user_cb), "Cancel", G_CALLBACK(free_add_user_data)); g_free(buffer); } typedef struct { PurpleAccountRequestAuthorizationCb auth_cb; PurpleAccountRequestAuthorizationCb deny_cb; void *data; char *username; char *alias; PurpleAccount *account; } auth_and_add; static void free_auth_and_add(auth_and_add *aa) { g_free(aa->username); g_free(aa->alias); g_free(aa); } static void authorize_and_add_cb(auth_and_add *aa) { aa->auth_cb(aa->data); purple_blist_request_add_buddy(aa->account, aa->username, NULL, aa->alias); free_auth_and_add(aa); } static void deny_no_add_cb(auth_and_add *aa) { aa->deny_cb(aa->data); free_auth_and_add(aa); } void *Account::request_authorize(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message, gboolean on_list, PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data) { char *buffer; PurpleConnection *gc; void *uihandle; gc = purple_account_get_connection(account); if (message != NULL && *message == '\0') message = NULL; buffer = g_strdup_printf("%s%s%s%s wants to add %s to his or her buddy list%s%s", remote_user, (alias != NULL ? " (" : ""), (alias != NULL ? alias : ""), (alias != NULL ? ")" : ""), (id != NULL ? id : (purple_connection_get_display_name(gc) != NULL ? purple_connection_get_display_name(gc) : purple_account_get_username(account))), (message != NULL ? ": " : "."), (message != NULL ? message : "")); if (!on_list) { auth_and_add *aa = g_new(auth_and_add, 1); aa->auth_cb = auth_cb; aa->deny_cb = deny_cb; aa->data = user_data; aa->username = g_strdup(remote_user); aa->alias = g_strdup(alias); aa->account = account; uihandle = purple_request_action(NULL, "Authorize buddy?", buffer, NULL, PURPLE_DEFAULT_ACTION_NONE, account, remote_user, NULL, aa, 2, "Authorize", authorize_and_add_cb, "Deny", deny_no_add_cb); } else { uihandle = purple_request_action(NULL, "Authorize buddy?", buffer, NULL, PURPLE_DEFAULT_ACTION_NONE, account, remote_user, NULL, user_data, 2, "Authorize", auth_cb, "Deny", deny_cb); } g_free(buffer); return uihandle; } void Account::request_close(void *uihandle) { purple_request_close(PURPLE_REQUEST_ACTION, uihandle); } void Account::connecting(PurpleConnection *gc, const char *text, size_t step, size_t step_count) { Account account = Account(gc->account); if(!step) b_log[W_INFO|W_SNO] << "Connection to " << account.getServername() << " in progress..."; else b_log[W_INFO|W_SNO] << "" << account.getID() << "(" << step << "/" << step_count-1 << "): " << text; } void Account::connected(PurpleConnection* gc) { Account account = Account(gc->account); account.removeReconnection(); irc::IRC* irc = Purple::getIM()->getIRC(); b_log[W_INFO|W_SNO] << "Connection to " << account.getServername() << " established!"; irc->addServer(new irc::RemoteServer(irc, account)); account.flushChannelJoins(); } void Account::account_signed_on_cb(PurpleConnection *gc, gpointer event) { Account account = Account(gc->account); GList* list = purple_get_chats(); /* Rejoin channels. */ for(; list; list = list->next) { PurpleChat *chat; GHashTable *comps = NULL; PurpleConversation *conv = (PurpleConversation*)list->data; Conversation c(conv); if(c.getAccount() != account || c.getType() != PURPLE_CONV_TYPE_CHAT || !purple_conversation_get_data(conv, "want-to-rejoin")) continue; chat = purple_blist_find_chat(purple_conversation_get_account(conv), purple_conversation_get_name(conv)); if (chat == NULL) { PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)); if (info->chat_info_defaults != NULL) comps = info->chat_info_defaults(gc, purple_conversation_get_name(conv)); } else { comps = purple_chat_get_components(chat); } serv_join_chat(gc, comps); if (chat == NULL && comps != NULL) g_hash_table_destroy(comps); } } void Account::disconnected(PurpleConnection* gc) { Account account = Account(gc->account); GList* list = purple_get_chats(); GList* next = NULL; account.abortChannelJoins(); /* Enqueue channels to auto-rejoin. */ for(; list; list = next) { Conversation c((PurpleConversation*)list->data); next = list->next; if(c.getAccount() != account) continue; if(purple_conv_chat_has_left(c.getPurpleChat())) { string name = c.getName(); c.leave(); account.enqueueChannelJoin(name); } else purple_conversation_set_data(c.getPurpleConversation(), "want-to-rejoin", GINT_TO_POINTER(TRUE)); } b_log[W_INFO|W_SNO] << "Closing link with " << account.getServername(); Purple::getIM()->getIRC()->removeServer(account.getServername()); } void Account::disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text) { Account acc(gc->account); b_log[W_ERR|W_SNO] << "Error(" << acc.getServername() << "): " << text; switch(reason) { case PURPLE_CONNECTION_ERROR_NETWORK_ERROR: case PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR: case PURPLE_CONNECTION_ERROR_OTHER_ERROR: b_log[W_ERR|W_SNO] << "Reconnection in " << acc.delayReconnect() << " seconds"; break; default: break; /* Do not auto reconnect. */ } } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/auth_local.cpp0000644000175000017500000000365511754151773016525 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "auth.h" #include "core/log.h" #include "core/util.h" #include "core/config.h" #include "irc/irc.h" #include "irc/user.h" #include "auth_local.h" namespace im { AuthLocal::AuthLocal(irc::IRC* _irc, const string& _username) : Auth(_irc, _username) { } bool AuthLocal::exists() { return im::IM::exists(username); } bool AuthLocal::authenticate(const string& password) { if (!im::IM::exists(username)) return false; im = new im::IM(irc, username); b_log[W_DEBUG] << "Authenticating user " << username << " using local database"; return im->getPassword() == password; } bool AuthLocal::setPassword(const string& password) { if(password.find(' ') != string::npos || password.size() < 8) { irc->notice(irc->getUser(), "Password must be at least 8 characters, and cannot contain whitespaces."); return false; } im->setPassword(password); return true; } string AuthLocal::getPassword() const { return im->getPassword(); } im::IM* AuthLocal::create(const string& password) { if (password.empty()) throw UnableToCreate("Please set a password for this new account"); return Auth::create(password); } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/plugin.cpp0000644000175000017500000000333011754151773015676 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "plugin.h" namespace im { Plugin::Plugin() : plug(0) { } Plugin::Plugin(PurplePlugin* _plug) : plug(_plug) { } string Plugin::getID() const { assert(isValid()); return plug->info->id; } string Plugin::getName() const { assert(isValid()); return plug->info->name; } string Plugin::getSummary() const { assert(isValid()); return plug->info->summary; } string Plugin::getDescription() const { assert(isValid()); return plug->info->description; } bool Plugin::isLoaded() const { assert(isValid()); return purple_plugin_is_loaded(plug); } void Plugin::load() { assert(isValid()); if (purple_plugin_is_unloadable(plug)) throw LoadError("Plugin is unloadable"); purple_plugin_load(plug); } void Plugin::unload() { assert(isValid()); if (purple_plugin_is_unloadable(plug)) throw LoadError("Plugin is unloadable"); if (!purple_plugin_unload(plug)) throw LoadError(string("Unable to unload the plugin: ") + plug->error); } }; /* ns im */ minbif-1.0.5+git20120508/src/im/roomlist.h0000644000175000017500000000255611754151773015726 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_ROOMLIST_H #define IM_ROOMLIST_H #include #include namespace im { using std::string; class RoomList { PurpleRoomlist* rlist; static PurpleRoomlistUiOps ui_ops; static void create(PurpleRoomlist* list); static void set_fields(PurpleRoomlist* list, GList* fields); static void add_room(PurpleRoomlist*, PurpleRoomlistRoom* room); static void in_progress(PurpleRoomlist* list, gboolean flag); static void destroy(PurpleRoomlist* list); public: static void init(); static void uninit(); RoomList(PurpleRoomlist* rl); ~RoomList(); }; } /* ns im */ #endif /* IM_ROOMLIST_H */ minbif-1.0.5+git20120508/src/im/buddy.h0000644000175000017500000000574111754151773015164 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_BUDDY_H #define IM_BUDDY_H #include #include #include "core/caca_image.h" namespace irc { class Buddy; }; namespace im { using std::string; class Account; /** This class represents a buddy. * * This class only interfaces between the minbif code * and a libpurple account object. */ class Buddy { PurpleBuddy* buddy; static void* getHandler(); static PurpleBlistUiOps blist_ui_ops; static void buddy_signonoff_cb(PurpleBuddy* buddy); static void update_node(PurpleBuddyList *list, PurpleBlistNode *node); static void removed_node(PurpleBuddyList *list, PurpleBlistNode *node); public: /** Initialization of libpurple buddies' stuffs. */ static void init(); static void uninit(); /** Empty constructor */ Buddy(); /** Create a Buddy instance * * @param buddy the libpurple's buddy instance. */ Buddy(PurpleBuddy* buddy); /** Comparaisons */ bool operator==(const Buddy& buddy) const; bool operator!=(const Buddy& buddy) const; bool isValid() const { return buddy != NULL; } /** Get username of buddy */ string getName() const; /** Get minbif alias */ string getAlias() const; /** Set minbif alias */ void setAlias(string alias, bool server_side = true) const; /** Get IM real name */ string getRealName() const; /** Get buddy group */ string getGroupName() const; /** Get IRC Nick object. */ irc::Buddy* getNick() const; /** Set IRC Nick object. */ void setNick(irc::Buddy* irc_buddy); /** Ask server to send server info. * * It'll be showed with notify_userinfo callback. */ void retrieveInfo() const; /** Buddy has been updated, so change his IRC status. */ void updated() const; /** Send a file to this buddy. */ void sendFile(string filename); bool isOnline() const; bool isAvailable() const; string getStatus() const; /** Get buddy's icon as a colored ASCII-art picture. * * @return an instance of CacaImage */ CacaImage getIcon() const; /** Get path to the icon. */ string getIconPath() const; PurpleGroup* getPurpleGroup() const; PurpleBuddy* getPurpleBuddy() const { return buddy; } /** Get account this buddy is from. */ Account getAccount() const; }; }; #endif /* IM_BUDDY_H */ minbif-1.0.5+git20120508/src/im/auth_pam.cpp0000644000175000017500000001166311754151773016206 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include "auth.h" #include "core/log.h" #include "core/util.h" #include "core/config.h" #include "irc/irc.h" #include "auth_pam.h" namespace im { AuthPAM::AuthPAM(irc::IRC* _irc, const string& _username) : Auth(_irc, _username) { pamh = NULL; } AuthPAM::~AuthPAM() { close(); } bool AuthPAM::exists() { return true; } static int pam_conv_func(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) { struct _pam_conv_func_data *func_data = (_pam_conv_func_data*) appdata_ptr; int count = 0; struct pam_response *reply; const char *reply_msg; reply = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response)); if (reply == NULL) { b_log[W_ERR] << "PAM: Could not allocate enough memory"; return PAM_CONV_ERR; } for (count = 0; count < num_msg; ++count) { switch (msgm[count]->msg_style) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: reply[count].resp_retcode = 0; /* sometimes the current password is asked first, sometimes not, how are we supposed to know ??? */ if (func_data->update && strstr(msgm[count]->msg, " new ")) reply_msg = (const char*) func_data->new_password.c_str(); else reply_msg = (const char*) func_data->password.c_str(); reply[count].resp = strndup(reply_msg, PAM_MAX_MSG_SIZE); b_log[W_DEBUG] << "PAM: msg " << count << ": " << msgm[count]->msg; b_log[W_DEBUG] << "PAM: msg " << count << ": " << reply_msg; break; case PAM_ERROR_MSG: b_log[W_ERR] << "PAM: " << msgm[count]->msg; break; case PAM_TEXT_INFO: b_log[W_DEBUG] << "PAM: " << msgm[count]->msg; break; default: b_log[W_ERR] << "PAM: erroneous conversation (" << msgm[count]->msg_style << ")"; goto failed_conversation; } } *response = reply; return PAM_SUCCESS; failed_conversation: for (count = 0; count < num_msg; ++count) { if (reply[count].resp != NULL) free(reply[count].resp); } free(reply); *response = NULL; return PAM_CONV_ERR; } bool AuthPAM::checkPassword(const string& password) { int retval; if (pamh) close(); pam_conversation.conv = pam_conv_func; pam_conversation.appdata_ptr = (void*) &pam_conv_func_data; pam_conv_func_data.update = false; pam_conv_func_data.password = password; retval = pam_start("minbif", username.c_str(), &pam_conversation, &pamh); if (retval == PAM_SUCCESS) { if (conf.GetSection("aaa")->GetItem("pam_setuid")->Boolean() == true) { struct passwd *pwd; pwd = getpwnam(username.c_str()); if (setuid(pwd->pw_uid) != 0) { b_log[W_ERR] << "Minbif needs to be launched in root for setuid_pam: "; close(); return false; } } retval = pam_authenticate(pamh, 0); /* is user really user? */ } if (retval == PAM_SUCCESS) retval = pam_acct_mgmt(pamh, 0); /* permitted access? */ if (retval != PAM_SUCCESS) { close(retval); return false; } return true; } bool AuthPAM::authenticate(const string& password) { b_log[W_DEBUG] << "Authenticating user " << username << " using PAM mechanism"; if(checkPassword(password)) { im = new im::IM(irc, username); return true; } return false; } void AuthPAM::close(int retval) { int retval2; if (!pamh) return; retval2 = pam_end(pamh, retval); if (retval2 != PAM_SUCCESS) /* close Linux-PAM */ throw IMError(string("PAM: Could not release authenticator: ") + pam_strerror(pamh, retval2)); pamh = NULL; } bool AuthPAM::setPassword(const string& password) { int retval; /* It should never happen */ if (!pamh) throw IMError("Cannot change password if not authenticated"); pam_conv_func_data.update = true; pam_conv_func_data.new_password = password; retval = pam_chauthtok(pamh, NULL); if (retval != PAM_SUCCESS) b_log[W_ERR] << "PAM: Password change failed: " << pam_strerror(pamh, retval); return (retval == PAM_SUCCESS); } string AuthPAM::getPassword() const { b_log[W_WARNING] << "Cannot fetch current password, it is hidden"; return ""; } im::IM* AuthPAM::create(const string& password) { if(!checkPassword(password)) throw IMError("PAM: Incorrect login/password"); return Auth::create(password); } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/ft.h0000644000175000017500000000333611754151773014464 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_FT_H #define IM_FT_H #include #include namespace im { using std::string; class Buddy; class FileTransfert { PurpleXfer* xfer; static PurpleXferUiOps ui_ops; static void new_xfer(PurpleXfer* xfer); static void destroy(PurpleXfer* xfer); static void add_xfer(PurpleXfer* xfer); static void update_progress(PurpleXfer* xfer, double percent); static void cancel_local(PurpleXfer* xfer); static void cancel_remote(PurpleXfer* xfer); public: static void init(); static void uninit(); FileTransfert(); FileTransfert(PurpleXfer* xfer); ~FileTransfert() {} bool operator==(const FileTransfert& ft); bool isValid() const { return xfer != NULL; } string getRemoteUser() const; Buddy getBuddy() const; string getFileName() const; string getLocalFileName() const; size_t getSize() const; size_t getSentBytes() const; bool isCompleted() const; bool isReceiving() const; bool isSending() const; }; }; #endif /* IM_FT_H */ minbif-1.0.5+git20120508/src/im/auth_connection.cpp0000644000175000017500000000401111754151773017555 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "auth.h" #include "core/log.h" #include "core/util.h" #include "core/config.h" #include "irc/irc.h" #include "irc/user.h" #include "auth_connection.h" namespace im { AuthConnection::AuthConnection(irc::IRC* _irc, const string& _username) : Auth(_irc, _username) { } bool AuthConnection::exists() { return true; } bool AuthConnection::authenticate(const string& password) { if (!im::IM::exists(username)) return false; sock::SockWrapper* sockw = irc->getSockWrap(); b_log[W_DEBUG] << "Authenticating user " << username << " using connection information"; if (sockw->GetClientUsername() == username) { im = new im::IM(irc, username); return true; } return false; } bool AuthConnection::setPassword(const string& password) { b_log[W_ERR] << "PAM: Password change failed: you are not authenticated using a password "; return false; } string AuthConnection::getPassword() const { b_log[W_WARNING] << "Cannot fetch current password, you are not authenticated using a password"; return ""; } im::IM* AuthConnection::create(const string& password) { if (irc->getSockWrap()->GetClientUsername() != username) throw IMError("No connection authentication"); return Auth::create(password); } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/protocol.h0000644000175000017500000000434311754151773015713 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_PROTOCOL_H #define IM_PROTOCOL_H #include #include #include #include "core/exception.h" using std::string; using std::map; namespace im { class Account; class Protocol { PurplePlugin* plugin; public: STREXCEPTION(OptionError); class Option { public: enum type_t { NONE, BOOL, STR, INT, ACCID, PASSWORD, STATUS_CHANNEL, SERVER_ALIASES }; private: enum type_t type; string name; string value; string text; string nameFromText(string s) const; public: Option(enum type_t type, string name, string text, string value = ""); Option(PurplePrefType type, string name, string text, string value = ""); Option(); bool operator==(string s) const; enum type_t getType() const { return type; } string getName() const { return name; } string getText() const { return text; } virtual void setValue(string v) { value = v; } string getValue() const { return value; } int getValueInt() const; bool getValueBool() const; }; typedef map Options; Protocol(PurplePlugin* plugin); Protocol(); bool operator==(const Protocol& proto) const; bool operator!=(const Protocol& proto) const; bool isValid() const { return plugin != NULL; } string getName() const; string getID() const; string getPurpleID() const; Options getOptions() const; PurplePluginProtocolInfo* getPurpleProtocol() const; }; }; /* namespace im */ #endif /* IM_PROTOCOL_H */ minbif-1.0.5+git20120508/src/im/auth_local.h0000644000175000017500000000230211754151773016156 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_AUTH_LOCAL_H #define IM_AUTH_LOCAL_H #include "auth.h" /** IM related classes */ namespace im { using std::string; class AuthLocal : public Auth { public: AuthLocal(irc::IRC* _irc, const string& _username); bool exists(); bool authenticate(const string& password); im::IM* create(const string& password); bool setPassword(const string& password); string getPassword() const; }; }; #endif /* IM_AUTH_LOCAL_H */ minbif-1.0.5+git20120508/src/im/ft.cpp0000644000175000017500000001124111754151773015011 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "im/purple.h" #include "im/im.h" #include "im/ft.h" #include "im/buddy.h" #include "irc/irc.h" #include "irc/buddy.h" #include "irc/dcc.h" #include "core/log.h" #include "core/config.h" namespace im { FileTransfert::FileTransfert() : xfer(NULL) {} FileTransfert::FileTransfert(PurpleXfer* _xfer) : xfer(_xfer) {} bool FileTransfert::operator==(const FileTransfert& ft) { return isValid() && ft.isValid() && this->xfer == ft.xfer; } string FileTransfert::getFileName() const { assert(isValid()); return purple_xfer_get_filename(xfer); } string FileTransfert::getLocalFileName() const { assert(isValid()); return purple_xfer_get_local_filename(xfer); } size_t FileTransfert::getSize() const { assert(isValid()); return purple_xfer_get_size(xfer); } size_t FileTransfert::getSentBytes() const { assert(isValid()); return purple_xfer_get_bytes_sent(xfer); } bool FileTransfert::isCompleted() const { assert(isValid()); return purple_xfer_is_completed(xfer); } bool FileTransfert::isReceiving() const { assert(isValid()); return purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE; } bool FileTransfert::isSending() const { assert(isValid()); return purple_xfer_get_type(xfer) == PURPLE_XFER_SEND; } string FileTransfert::getRemoteUser() const { assert(isValid()); return purple_xfer_get_remote_user(xfer); } Buddy FileTransfert::getBuddy() const { assert(isValid()); return Buddy(purple_find_buddy(xfer->account, xfer->who)); } /* STATIC */ void FileTransfert::new_xfer(PurpleXfer* xfer) { } void FileTransfert::destroy(PurpleXfer* xfer) { FileTransfert ft(xfer); Purple::getIM()->getIRC()->updateDCC(ft, true); if(ft.isCompleted()) { if(ft.isSending()) b_log[W_INFO|W_SNO] << "File " << ft.getFileName() << " sent to " << ft.getRemoteUser(); else if(conf.GetSection("file_transfers")->GetItem("dcc")->Boolean() == false) b_log[W_INFO|W_SNO] << "File saved as: " << ft.getLocalFileName(); } } void FileTransfert::add_xfer(PurpleXfer* xfer) { irc::ConvNick* n = NULL; irc::IRC* irc = Purple::getIM()->getIRC(); FileTransfert ft(xfer); Buddy buddy = ft.getBuddy(); if(buddy.isValid()) n = irc->getNick(Buddy(buddy)); else { /* TODO find the chat buddy or someone else. */ } if(ft.isReceiving()) { b_log[W_INFO|W_SNO] << "Starting receiving file " << ft.getFileName() << " from " << ft.getRemoteUser(); /* Do not send file to IRC user with DCC if this feature is disabled. */ if(conf.GetSection("file_transfers")->GetItem("dcc")->Boolean() == false) return; try { irc->createDCCSend(ft, n); } catch(irc::DCCListenError &e) { b_log[W_SNO|W_ERR] << "Unable to listen for DCC, you'll might retrieve yourself the file."; } } else { b_log[W_INFO|W_SNO] << "Starting sending file " << ft.getFileName() << " to " << ft.getRemoteUser(); } } void FileTransfert::update_progress(PurpleXfer* xfer, double percent) { /* Note: 0 <= percent <= 1 */ if(conf.GetSection("file_transfers")->GetItem("dcc")->Boolean()) Purple::getIM()->getIRC()->updateDCC(FileTransfert(xfer)); } void FileTransfert::cancel_local(PurpleXfer* xfer) { FileTransfert ft(xfer); if(ft.isSending()) b_log[W_SNO|W_ERR] << "Aborted sending file " << ft.getFileName() << " to " << ft.getRemoteUser(); else b_log[W_SNO|W_ERR] << "Aborted receiving file " << ft.getFileName() << " from " << ft.getRemoteUser(); } void FileTransfert::cancel_remote(PurpleXfer* xfer) { FileTransfert ft(xfer); if(ft.isSending()) b_log[W_SNO|W_ERR] << ft.getRemoteUser() << " aborted receiving file " << ft.getFileName(); else b_log[W_SNO|W_ERR] << ft.getRemoteUser() << " aborted sending file " << ft.getFileName(); } PurpleXferUiOps FileTransfert::ui_ops = { new_xfer, destroy, add_xfer, update_progress, cancel_local, cancel_remote, /* padding */ NULL, NULL, NULL, NULL }; void FileTransfert::init() { purple_xfers_set_ui_ops(&ui_ops); } void FileTransfert::uninit() { purple_xfers_set_ui_ops(NULL); } }; /* ns im */ minbif-1.0.5+git20120508/src/im/im.h0000644000175000017500000000776311754151773014470 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_IM_H #define IM_IM_H #include #include #include "account.h" #include "protocol.h" #include "plugin.h" #include "core/log.h" namespace irc { class IRC; }; /** IM related classes */ namespace im { using std::string; using std::map; /** Raised when IM can't be initialized. */ LOGEXCEPTION(IMError); /** Protocol is unknown */ EXCEPTION(ProtocolUnknown); /** Class used to Instant Messaging things */ class IM { static string path; public: static void setPath(const string& path); static bool exists(const string& username); private: string username; string user_path; irc::IRC* irc; public: /** Constructor. * * @param irc pointer to the IRC instance * @param username user name */ IM(irc::IRC* irc, string username); ~IM(); /** Restore previous status */ void restore(); /** Get path to user settings */ string getUserPath() const { return user_path; } /** Get username of this account. */ string getUsername() const { return username; } /** Set user password */ void setPassword(const string& password); string getPassword() const; /** Set typing notice */ void setTypingNotice(bool enabled); bool hasTypingNotice() const; /** Accept messages from people who are not in buddy list */ void setAcceptNoBuddiesMessages(bool enabled); bool hasAcceptNoBuddiesMessages() const; /** Delay before sending a message */ void setSendDelay(int delay); int getSendDelay() const; /** Set voiced buddies */ void setVoicedBuddies(bool enabled); bool hasVoicedBuddies() const; /** Does it store aliases server side. */ void setServerAliases(bool enabled); bool hasServerAliases() const; /** Set away idle */ void setAwayIdle(bool enabled); bool hasAwayIdle() const; /** Set path to the buddy icon. * * This buddy icon will used on every IM accounts. */ void setBuddyIcon(const string& path); /** Get path to current buddy icon. */ string getBuddyIconPath() const; irc::IRC* getIRC() const { return irc; } /** Get all plugins available. */ map getPluginsList() const; /** Get list of protocols in a map. */ map getProtocolsList() const; /** Get a protocol from id * * @param id protocol's id * @return a Protocol instance */ Protocol getProtocol(string id) const; /** Get list of accounts in a map. */ map getAccountsList() const; /** Get an account from name */ Account getAccount(string name) const; /** Get an account from status channel name. */ Account getAccountFromChannel(string name) const; /** Create an account. * * @param proto protocol used by this account * @param username username of this account * @param options list of specific options of this protocol * @param register_account register this account on server * @return an Account instance. */ Account addAccount(const Protocol& proto, const string& username, const Protocol::Options& options, bool register_account = false); /** Remove an account. * * @param account Account instance */ void delAccount(Account account); /** Set status on IM accounts */ bool setStatus(string status); /** Is away? */ bool isAway() const; }; }; #endif /* IM_IM_H */ minbif-1.0.5+git20120508/src/im/auth.h0000644000175000017500000000326211754151773015012 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_AUTH_H #define IM_AUTH_H #include #include "im.h" #include "core/exception.h" namespace irc { class IRC; }; /** IM related classes */ namespace im { using std::string; STREXCEPTION(UnableToCreate); class Auth { public: static Auth* validate(irc::IRC* irc, const string& username, const string& password); static Auth* generate(irc::IRC* irc, const string& username, const string& password); Auth(irc::IRC* _irc, const string& _username); virtual ~Auth() {} virtual bool exists() = 0; virtual bool authenticate(const string& password) = 0; virtual im::IM* create(const string& password); im::IM* getIM() { return im; }; virtual bool setPassword(const string& password) = 0; virtual string getPassword() const = 0; protected: static vector getMechanisms(irc::IRC* irc, const string& username); string username; irc::IRC* irc; im::IM *im; }; }; #endif /* IM_AUTH_H */ minbif-1.0.5+git20120508/src/im/protocol.cpp0000644000175000017500000001001611754151773016240 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "protocol.h" #include "account.h" #include "core/util.h" namespace im { Protocol::Option::Option() : type(Protocol::Option::NONE) {} string Protocol::Option::nameFromText(string s) const { FOREACH(string, s, it) if(*it == ' ') *it = '-'; else *it = (char)tolower(*it); return s; } Protocol::Option::Option(Protocol::Option::type_t _type, string _name, string _text, string _value) : type(_type), name(_name), value(_value), text(_text) {} Protocol::Option::Option(PurplePrefType _type, string _name, string _text, string _value) : name(_name), value(_value), text(_text) { switch(_type) { case PURPLE_PREF_BOOLEAN: type = Protocol::Option::BOOL; break; case PURPLE_PREF_STRING: type = Protocol::Option::STR; break; case PURPLE_PREF_INT: type = Protocol::Option::INT; break; default: type = Protocol::Option::NONE; break; } } int Protocol::Option::getValueInt() const { return s2t(value); } bool Protocol::Option::getValueBool() const { return (value == "true" || value == "1"); } bool Protocol::Option::operator==(string s) const { return s == name; } Protocol::Protocol() : plugin(NULL) {} Protocol::Protocol(PurplePlugin* _plugin) : plugin(_plugin) {} bool Protocol::operator==(const Protocol& proto) const { return proto.getPurpleID() == getPurpleID(); } bool Protocol::operator!=(const Protocol& proto) const { return proto.getPurpleID() != getPurpleID(); } string Protocol::getName() const { assert(plugin); return plugin->info->name; } string Protocol::getID() const { assert(plugin); if(!strncmp(plugin->info->id, "prpl-", 5)) return plugin->info->id + 5; else return plugin->info->id; } string Protocol::getPurpleID() const { assert(plugin); return plugin->info->id; } Protocol::Options Protocol::getOptions() const { Options options; PurplePluginProtocolInfo* prplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); GList *iter; for (iter = prplinfo->protocol_options; iter; iter = iter->next) { PurpleAccountOption *option = static_cast(iter->data); PurplePrefType type = purple_account_option_get_type(option); Option opt = Option(type, purple_account_option_get_setting(option), purple_account_option_get_text(option)); switch(type) { case PURPLE_PREF_BOOLEAN: opt.setValue(purple_account_option_get_default_bool(option) ? "true" : "false"); break; case PURPLE_PREF_STRING: { const char* s = purple_account_option_get_default_string(option); if(s && *s) opt.setValue(s); break; } case PURPLE_PREF_INT: opt.setValue(t2s(purple_account_option_get_default_int(option))); break; default: /* This option is not supported */ continue; } options[opt.getName()] = opt; } options["accid"] = Option(Option::ACCID, "accid", "Account ID"); options["status_channel"] = Option(Option::STATUS_CHANNEL, "status_channel", "Status Channel", "&minbif"); options["password"] = Option(Option::PASSWORD, "password", "Account password"); options["server_aliases"] = Option(Option::SERVER_ALIASES, "server_aliases", "Store aliases server-side", "true"); return options; } PurplePluginProtocolInfo* Protocol::getPurpleProtocol() const { return PURPLE_PLUGIN_PROTOCOL_INFO(plugin); } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/conversation.h0000644000175000017500000001310411754151773016557 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_CONVERSATION_H #define IM_CONVERSATION_H #include #include namespace irc { class ConvNick; class ConversationChannel; } namespace im { using std::string; class Account; class Buddy; class ChatBuddy; class Conversation { PurpleConversation* conv; static void* getHandler(); static PurpleConversationUiOps conv_ui_ops; static void create(PurpleConversation*); static void destroy(PurpleConversation*); static void conv_present(PurpleConversation*); static void write_im(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime); static void write_conv(PurpleConversation *conv, const char *who, const char* alias, const char *message, PurpleMessageFlags flags, time_t mtime); static void add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals); static void update_user(PurpleConversation* c, const char* user); static void remove_user(PurpleConversation* conv, const char* cbname, const char *reason); static void topic_changed(PurpleConversation* conv, const char* who, const char* topic); static void buddy_typing(PurpleAccount* account, const char* who, gpointer null); static void chat_rename_user(PurpleConversation *conv, const char *old, const char *new_n, const char *new_a); public: static void init(); static void uninit(); Conversation(); Conversation(const Account& account, const Buddy& buddy); Conversation(const Account& account, const ChatBuddy& cbuddy); Conversation(const Account& account, const string& name); Conversation(PurpleConversation* conv); bool operator==(const Conversation& conv) const; bool operator!=(const Conversation& conv) const; bool isValid() const { return conv != NULL; } PurpleConversation* getPurpleConversation() const { return conv; } PurpleConvChat* getPurpleChat() const; PurpleConvIm* getPurpleIm() const; string getName() const; /** Get a normalized IRC channel name. * * On IRC, channels name must begin with '#' and are uniq. * * For a given channel on a given account, the IRC channel name * will be: "#name:accid". * * @param name IM channel name * @param acc account which hosts this channel * @return an IRC channel name string. */ static string normalizeIRCName(string name, const Account& acc); string getChanName() const; string getChanTopic() const; Account getAccount() const; PurpleConversationType getType() const; /** Set the irc::Nick* object associated to this conversation. * * It is valid only if this is an IM conversation. * * @param n irc::Nick object * @param purge_unknown if the current associated nick is an * irc::UnknownBuddy object, purge it * from IRC. */ void setNick(irc::ConvNick* n, bool purge_unknown = true); /** Get the irc::Nick* object associated to this conversation. */ irc::ConvNick* getNick() const; void setChannel(irc::ConversationChannel* c) const; irc::ConversationChannel* getChannel() const; /** Create the IRC channel associated to this conversation. */ void createChannel() const; /** Destroy the IRC channel associated to this conversation. */ void destroyChannel() const; /** Present the conversation to user. */ void present() const; /** Leave the conversation. */ void leave(); /** Send a command to this conversation. */ int sendCommand(const string& cmd) const; /** Send a message in this conversation. * * @param text text to send. */ void sendMessage(string text); /** A message is received, this function present it to user. * * @param from nickname of sender * @param text text message * @param action this is an action (/me) */ void recvMessage(string from, string text, bool action = false); /** Invite a buddy into this conversation. * * This is available only for the CHAT conversations. * * @param buddy buddy's name * @param message message to send with invitation. */ void invite(const string& buddy, const string& message); /** Set topic of conversation. */ bool setTopic(const string& topic); }; class ChatBuddy { Conversation conv; PurpleConvChatBuddy* cbuddy; public: ChatBuddy(); ChatBuddy(Conversation conv, PurpleConvChatBuddy* cbuddy); bool operator==(const ChatBuddy& cbuddy) const; bool operator!=(const ChatBuddy& cbuddy) const; bool operator<(const ChatBuddy& cbuddy) const; bool isValid() const { return cbuddy != NULL; } string getName() const; string getAlias() const; string getRealName() const; bool isMe() const; int getChanStatus() const; Conversation getConversation() const { return conv; } Account getAccount() const; PurpleConvChatBuddy* getPurpleChatBuddy() const { return cbuddy; } }; } #endif /* IM_CONVERSATION_H */ minbif-1.0.5+git20120508/src/im/media.h0000644000175000017500000000562411754151773015134 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_MEDIA_H #define IM_MEDIA_H #include #include #include #include "core/mutex.h" #ifdef HAVE_VIDEO #include #include "im/buddy.h" #include "core/caca_image.h" #endif class _CallBack; namespace irc { class DCCChat; }; namespace im { using std::vector; using std::string; #ifdef HAVE_VIDEO class Media; class MediaList : public Mutex { vector medias; _CallBack* check_cb; int check_id; public: MediaList(); ~MediaList(); void addMedia(const Media& media); void removeMedia(const Media& media); Media getMedia(PurpleMedia* m); void enqueueBuffer(const Media& media, const CacaImage& buf); bool check(void*); }; #endif /* HAVE_VIDEO */ class Media { #ifdef HAVE_VIDEO PurpleMedia* media; Buddy buddy; vector queue; irc::DCCChat* dcc; static MediaList media_list; static bool gstreamer_init_failed; static GstElement *create_default_video_src(PurpleMedia *media, const gchar *session_id, const gchar *participant); static GstElement *create_default_video_sink(PurpleMedia *media, const gchar *session_id, const gchar *participant); static gboolean media_new_cb(PurpleMediaManager *manager, PurpleMedia *media, PurpleAccount *account, gchar *screenname, gpointer nul); static gboolean minbif_media_ready_cb(PurpleMedia *media); static void minbif_media_state_changed_cb(PurpleMedia *media, PurpleMediaState state, gchar *sid, gchar *name, void* gtkmedia); static void got_data(GstElement* object, GstBuffer* buffer, GstPad* arg1, gpointer user_data); #endif /* HAVE_VIDEO */ public: static void init(); static void uninit(); #ifdef HAVE_VIDEO Media(); Media(PurpleMedia*); Media(PurpleMedia*, const Buddy& b); Media(const Media&); ~Media(); Media& operator=(const Media&); bool operator==(const Media&) const; bool operator!=(const Media&) const; bool isValid() const { return media; } void enqueueBuffer(const CacaImage& buf); void checkBuffer(); Buddy getBuddy() const { return buddy; } PurpleMedia* getPurpleMedia() const { return media; } #endif /* HAVE_VIDEO */ }; }; /* ns im */ #endif /* IM_MEDIA_H */ minbif-1.0.5+git20120508/src/im/plugin.h0000644000175000017500000000241311754151773015344 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_PLUGIN_H #define IM_PLUGIN_H #include #include #include "core/exception.h" namespace im { using std::string; class Plugin { PurplePlugin* plug; public: STREXCEPTION(LoadError); Plugin(); Plugin(PurplePlugin* plug); bool isValid() const { return plug != NULL; } string getID() const; string getName() const; string getSummary() const; string getDescription() const; bool isLoaded() const; void load(); void unload(); }; }; /* ns im */ #endif /* IM_PLUGIN_H */ minbif-1.0.5+git20120508/src/im/auth.cpp0000644000175000017500000000636111754151773015350 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "auth.h" #include "core/log.h" #include "core/util.h" #include "core/config.h" #include "irc/irc.h" #include "auth_local.h" #include "auth_connection.h" #ifdef HAVE_PAM # include "auth_pam.h" #endif namespace im { template static Auth* authFactory(irc::IRC* irc, const string& username) { return new T(irc, username); } static struct { const char* config_param; Auth* (*factory) (irc::IRC* irc, const string& username); } auth_mechanisms[] = { { "use_connection", authFactory }, #ifdef HAVE_PAM { "use_pam", authFactory }, #endif { "use_local", authFactory } }; vector Auth::getMechanisms(irc::IRC* irc, const string& username) { vector mechanisms; for(size_t i = 0; i < (sizeof auth_mechanisms / sizeof *auth_mechanisms); ++i) if (conf.GetSection("aaa")->GetItem(auth_mechanisms[i].config_param)->Boolean()) mechanisms.push_back(auth_mechanisms[i].factory(irc, username)); return mechanisms; } Auth* Auth::validate(irc::IRC* irc, const string& username, const string& password) { vector mechanisms = getMechanisms(irc, username); Auth* mech_ok = NULL; if (mechanisms.empty()) throw IMError("Login disabled (please consult your administrator)"); for (vector::iterator m = mechanisms.begin(); m != mechanisms.end(); ++m) { if ((mech_ok == NULL) && (*m)->exists() && (*m)->authenticate(password)) mech_ok = *m; else delete *m; } return mech_ok; } Auth* Auth::generate(irc::IRC* irc, const string& username, const string& password) { vector mechanisms = getMechanisms(irc, username); Auth* mech_ok = NULL; string err; if (mechanisms.empty()) throw IMError("Private server: account creation disabled"); for (vector::iterator m = mechanisms.begin(); m != mechanisms.end(); ++m) { try { if ((mech_ok == NULL) && (*m)->create(password)) { mech_ok = *m; continue; } } catch(UnableToCreate &e) { err = e.Reason(); } delete *m; } if (!mech_ok && !err.empty()) throw IMError("Unable to create account: " + err); return mech_ok; } Auth::Auth(irc::IRC* _irc, const string& _username) : username(_username), irc(_irc), im(NULL) { } im::IM* Auth::create(const string& password) { if (exists()) throw UnableToCreate("Already exists."); b_log[W_DEBUG] << "Creating user " << username; im = new im::IM(irc, username); setPassword(password); return im; } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/account.h0000644000175000017500000001523611754151773015511 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_ACCOUNT_H #define IM_ACCOUNT_H #include #include #include #include #include "im/protocol.h" namespace irc { class StatusChannel; } namespace im { using std::string; using std::vector; using std::map; class Buddy; /** This class represents an account. * * This class only interfaces between the minbif code * and a libpurple account object. */ class Account { PurpleAccount* account; Protocol proto; static PurpleConnectionUiOps conn_ops; static PurpleAccountUiOps acc_ops; static void* getHandler(); static void account_signed_on_cb(PurpleConnection *gc, gpointer event); static void account_added(PurpleAccount*); static void account_removed(PurpleAccount*); static void notify_added(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *msg); static void request_add(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *msg); static void *request_authorize(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message, gboolean on_list, PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data); static void request_close(void *uihandle); static void connecting(PurpleConnection *gc, const char *text, size_t step, size_t step_count); static void connected(PurpleConnection* gc); static void disconnected(PurpleConnection* gc); static void disconnect_reason(PurpleConnection *gc, PurpleConnectionError reason, const char *text); static gboolean reconnect(void*); public: /** Initialization of libpurple accounts' stuffs. */ static void init(); static void uninit(); /** Empty constructor */ Account(); /** Create an account instance. * * @param account the libpurple's account object. * @param proto optional argument to provide the protocol object. */ Account(PurpleAccount* account, Protocol proto = Protocol()); /** Comparaison between two accounts */ bool operator==(const Account&) const; bool operator!=(const Account&) const; bool isValid() const { return account != NULL; } PurpleAccount* getPurpleAccount() const { return account; } PurpleConnection* getPurpleConnection() const; /** Get ID of account. * * @param calculate_newone if true, when the ID is not set, calculate a new one. * @return a string un form \a "" */ string getID(bool calculate_newone = true) const; void setID(string id) const; /** Get username of this account */ string getUsername() const; /** Get password of this account */ string getPassword() const; void setPassword(string password); /** Enable/Disable store of aliases server-side. */ void setServerAliases(bool b); bool hasServerAliases() const; /** Register account on server. */ void registerAccount() const; Protocol::Options getOptions() const; void setOptions(const Protocol::Options& options); /** Set path to the buddy icon to use for this account. */ void setBuddyIcon(string filename); /** Set status */ void setStatus(PurpleStatusPrimitive pri, string message = ""); /** Get status */ PurpleStatusPrimitive getStatus() const; /** Get status message */ string getStatusMessage(PurpleStatusPrimitive pri = PURPLE_STATUS_UNSET) const; /** Get room list from this account. * The roomlist callbacks will be called. */ void displayRoomList() const; /** Get name of IRC server linked to this account. * * @return a string in form \a ":" */ string getServername() const; /** Get status channel name */ string getStatusChannelName() const; /** Set status channel name */ void setStatusChannelName(const string& c); /** Get the Status Channel instance. */ irc::StatusChannel* getStatusChannel() const; /** Enqueue a channel name to join at connection. */ void enqueueChannelJoin(const string& c); /** Try to join every channels in queue. * It also remove them from queue. */ void flushChannelJoins(); /** Clear the channel joins queue without trying to join them. */ void abortChannelJoins(); Protocol getProtocol() const { return proto; } bool isConnected() const; bool isConnecting() const; /** Auto reconnect to this account with a delay. */ int delayReconnect() const; /** Abort the auto-reconnection. */ void removeReconnection(bool verbose = false) const; /** Get list of available commands. */ vector getCommandsList() const; /** Call a command. */ bool callCommand(const string& command) const; /** \todo TODO implement it */ vector getBuddies() const; /** All buddiers are updated */ void updatedAllBuddies() const; /** Connect account */ void connect() const; /** Disconnect account */ void disconnect() const; /** Create the status channel on the IRC network */ void createStatusChannel(); /** Leave the status channel */ void leaveStatusChannel(); /** Get list of denied people */ vector getDenyList() const; /** Deny a user. */ bool deny(const string& who) const; /** Allow a user. */ bool allow(const string& who) const; /** Add a buddy on this account * * @param username user name * @param group group name */ void addBuddy(const string& username, const string& group) const; /** Remove a buddy from this account * * @param buddy Buddy's instance */ void removeBuddy(Buddy buddy) const; /** Does this account support chats? */ bool supportsChats() const; /** Join a chat */ bool joinChat(const string& name, const string& parameters) const; /** Get parameters list */ map getChatParameters() const; }; }; #endif /* IM_ACCOUNT_H */ minbif-1.0.5+git20120508/src/im/auth_connection.h0000644000175000017500000000233311754151773017227 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_AUTH_CONNECTION_H #define IM_AUTH_CONNECTION_H #include "auth.h" /** IM related classes */ namespace im { using std::string; class AuthConnection : public Auth { public: AuthConnection(irc::IRC* _irc, const string& _username); bool exists(); bool authenticate(const string& password); im::IM* create(const string& password); bool setPassword(const string& password); string getPassword() const; }; }; #endif /* IM_AUTH_CONNECTION_H */ minbif-1.0.5+git20120508/src/im/purple.h0000644000175000017500000000474711754151773015371 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_PURPLE_H #define IM_PURPLE_H #include #include #include "plugin.h" #include "protocol.h" #include "account.h" #include "core/log.h" namespace im { using std::map; using std::string; class IM; LOGEXCEPTION(PurpleError); /** Static class to interface with libpurple. * * Problem with libpurple is that it supports only one * username per process, as there are ugly static variables * to store states. * * This class is used to interface the POO-style of minbif * with the poor C-static-style of libpurple. */ class Purple { /** Instanciation is forbidden */ Purple() {} ~Purple() {} static IM* im; static void inited(); static GHashTable *ui_info; static PurpleEventLoopUiOps eventloop_ops; static PurpleCoreUiOps core_ops; static PurpleDebugUiOps debug_ops; static GHashTable *minbif_ui_get_info(void); static void minbif_prefs_init(); static void debug_init(); static void debug(PurpleDebugLevel level, const char *category, const char *args); public: /** Initialization */ static void init(IM* im); /** Uninitialization */ static void uninit(); static IM* getIM() { return im; } static map getPluginsList(); /** Get protocols list * * @return map with first=id, second=Protocol object */ static map getProtocolsList(); static Protocol getProtocolByPurpleID(string id); static map getAccountsList(); static Account addAccount(const Protocol& proto, const string& username, const Protocol::Options& options, bool register_account); static void delAccount(PurpleAccount* account); static string getNewAccountName(Protocol proto, const Account& butone = Account()); }; }; #endif /* IM_PURPLE_H */ minbif-1.0.5+git20120508/src/im/buddy.cpp0000644000175000017500000002076411754151773015521 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "im/purple.h" #include "im/buddy.h" #include "im/account.h" #include "im/im.h" #include "core/util.h" #include "core/log.h" #include "irc/buddy.h" #include "irc/irc.h" #include "irc/channel.h" #include "irc/status_channel.h" namespace im { Buddy::Buddy() : buddy(NULL) {} Buddy::Buddy(PurpleBuddy* _buddy) : buddy(_buddy) { } bool Buddy::operator==(const Buddy& buddy) const { return this->isValid() && buddy.isValid() && buddy.buddy == this->buddy; } bool Buddy::operator!=(const Buddy& buddy) const { return !isValid() || !buddy.isValid() || buddy.buddy != this->buddy; } string Buddy::getName() const { assert(isValid()); const char* n = buddy->name; if(n) return n; else return ""; } string Buddy::getAlias() const { assert(isValid()); const char* a = purple_buddy_get_alias(buddy); if(a && *a) return a; else return getName(); } void Buddy::setAlias(string alias, bool server_side) const { assert(isValid()); purple_blist_alias_buddy(buddy, alias.c_str()); /* XXX * If this method is called while adding a new buddy in local * blist, and if the email address doesn't exist, there is a race * condition because the buddy isn't added on the MSN server, * and the fucking libpurple's MSN plugin doesn't check it and crashes. * * It isn't possible to avoid sending alias to server, because at * connection, local aliases are overrided by server aliases. * * So, for *now* (and until the pidgin crash will be fixed), * I assume that minbif'll crash in this case, and I don't care * about, you've had to correctly typing, fool! * * Ref: http://developer.pidgin.im/ticket/10393 */ if (server_side && Purple::getIM()->hasServerAliases() && getAccount().hasServerAliases()) serv_alias_buddy(buddy); } irc::Buddy* Buddy::getNick() const { assert(isValid()); PurpleBlistNode* node = (PurpleBlistNode*)buddy; return static_cast(node->ui_data); } void Buddy::setNick(irc::Buddy* b) { assert(isValid()); PurpleBlistNode* node = (PurpleBlistNode*)buddy; node->ui_data = b; } string Buddy::getGroupName() const { assert(isValid()); PurpleGroup* group = purple_buddy_get_group(buddy); return group && group->name ? group->name : ""; } void Buddy::retrieveInfo() const { assert(isValid()); assert(buddy->account != NULL); serv_get_info(purple_account_get_connection(buddy->account), purple_buddy_get_name(buddy)); } void Buddy::sendFile(string filename) { assert(isValid()); serv_send_file(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy), filename.c_str()); } void Buddy::updated() const { irc::StatusChannel* chan = getAccount().getStatusChannel(); if(!chan) return; irc::Buddy* n = getNick(); if(!n) n = dynamic_cast(Purple::getIM()->getIRC()->getNick(*this)); if(!n) return; if(isOnline()) { bool available = isAvailable() && Purple::getIM()->hasVoicedBuddies(); irc::ChanUser* chanuser = n->getChanUser(chan); if(!chanuser) n->join(chan, available ? irc::ChanUser::VOICE : 0); else if(available ^ chanuser->hasStatus(irc::ChanUser::VOICE)) { if(available) chan->setMode(Purple::getIM()->getIRC(), irc::ChanUser::VOICE, chanuser); else chan->delMode(Purple::getIM()->getIRC(), irc::ChanUser::VOICE, chanuser); } } else if(n->isOn(chan)) n->quit("Signed-Off"); } string Buddy::getRealName() const { assert(isValid()); const char* rn = purple_buddy_get_server_alias(buddy); if(rn && *rn) return rn; else { const char* servernick = purple_blist_node_get_string((PurpleBlistNode*)buddy, "servernick"); if(servernick && *servernick) return servernick; else return getName(); } } bool Buddy::isOnline() const { assert(isValid()); return PURPLE_BUDDY_IS_ONLINE(buddy); } bool Buddy::isAvailable() const { assert(isValid()); return purple_presence_is_available(purple_buddy_get_presence(buddy)); } string Buddy::getStatus() const { PurpleStatus* status = purple_presence_get_active_status(buddy->presence); if(!status) return ""; string s; if (!isAvailable()) s = purple_status_get_name(status); PurplePlugin* prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account)); PurplePluginProtocolInfo* prpl_info = NULL; if(prpl) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); if(prpl_info && prpl_info->status_text && buddy->account->gc) { char* tmp = prpl_info->status_text(buddy); const char* end; if(tmp) { if(!g_utf8_validate(tmp, -1, &end)) { char* utf8 = g_strndup(tmp, g_utf8_pointer_to_offset(tmp, end)); g_free(tmp); tmp = utf8; } char* tmp2 = purple_markup_strip_html(tmp); if (!s.empty()) s += ": "; s += tmp2; g_free(tmp2); g_free(tmp); } } return s; } Account Buddy::getAccount() const { assert(isValid()); return Account(buddy->account); } CacaImage Buddy::getIcon() const { assert(isValid()); PurpleBuddyIcon* bicon = purple_buddy_icons_find(buddy->account, buddy->name);//purple_buddy_get_icon(buddy); if(!bicon) return CacaImage(); return CacaImage(p2s(purple_buddy_icon_get_full_path(bicon))); } string Buddy::getIconPath() const { assert(isValid()); PurpleBuddyIcon* bicon = purple_buddy_icons_find(buddy->account, buddy->name);//purple_buddy_get_icon(buddy); if(!bicon) return ""; return p2s(purple_buddy_icon_get_full_path(bicon)); } PurpleGroup* Buddy::getPurpleGroup() const { assert(isValid()); return purple_buddy_get_group(buddy); } /* STATIC */ PurpleBlistUiOps Buddy::blist_ui_ops = { NULL,//new_list, NULL,//new_node, NULL,//blist_show, Buddy::update_node,//node_update, Buddy::removed_node,//node_remove, NULL, NULL, NULL,//finch_request_add_buddy, NULL,//finch_request_add_chat, NULL,//finch_request_add_group, NULL, NULL, NULL, NULL }; void Buddy::init() { purple_blist_set_ui_ops(&blist_ui_ops); purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on", getHandler(), PURPLE_CALLBACK(buddy_signonoff_cb), NULL); purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off", getHandler(), PURPLE_CALLBACK(buddy_signonoff_cb), NULL); } void Buddy::uninit() { purple_blist_set_ui_ops(NULL); } void* Buddy::getHandler() { static int handler; return &handler; } void Buddy::buddy_signonoff_cb(PurpleBuddy* buddy) { update_node(NULL, (PurpleBlistNode*)buddy); } void Buddy::update_node(PurpleBuddyList *list, PurpleBlistNode *node) { if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { Buddy buddy = Buddy((PurpleBuddy*)node); irc::Buddy* n = buddy.getNick(); if(!n) { irc::Server* server = Purple::getIM()->getIRC()->getServer(buddy.getAccount().getServername()); if(!server) return; n = new irc::Buddy(server, buddy); while(Purple::getIM()->getIRC()->getNick(n->getNickname())) n->setNickname(n->getNickname() + "_"); Purple::getIM()->getIRC()->addNick(n); } /* If server overrides the IRC nickname as alias, force it. * WARN! This function probably recalls this one, so it is * really NECESSARY to call them at end of this block. * If n is not in IRC's user list, two irc::Buddy * instances will be inserted, with one lost. */ if(buddy.getAlias() != n->getNickname()) buddy.setAlias(n->getNickname(), false); buddy.updated(); } } void Buddy::removed_node(PurpleBuddyList *list, PurpleBlistNode *node) { purple_request_close_with_handle(node); if (node->parent) update_node(list, node->parent); if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { Buddy buddy = Buddy((PurpleBuddy*)node); irc::Buddy* n = buddy.getNick(); if(!n) n = dynamic_cast(Purple::getIM()->getIRC()->getNick(buddy)); if(n) { n->quit("Removed"); Purple::getIM()->getIRC()->removeNick(n->getNickname()); } } } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/purple.cpp0000644000175000017500000001734211754151773015717 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "purple.h" #include "im.h" #include "buddy.h" #include "conversation.h" #include "request.h" #include "roomlist.h" #include "ft.h" #include "media.h" #include "irc/irc.h" #include "irc/buddy_icon.h" #include "core/version.h" #include "core/log.h" #include "core/util.h" #include "core/config.h" namespace im { IM* Purple::im = NULL; PurpleEventLoopUiOps Purple::eventloop_ops = { /* timeout_add */ g_timeout_add, /* timeout_remove */ g_source_remove, /* input_add */ glib_input_add, /* input_remove */ g_source_remove, /* input_get_error*/ NULL, /* timeout_add_seconds */ NULL, NULL, NULL, NULL }; void Purple::debug(PurpleDebugLevel level, const char *category, const char *args) { switch(level) { case PURPLE_DEBUG_FATAL: b_log[W_ERR] << "[" << category << "] " << args; break; case PURPLE_DEBUG_ERROR: b_log[W_PURPLE] << "[" << category << "] " << args; break; case PURPLE_DEBUG_WARNING: b_log[W_DEBUG] << "[" << category << "] " << args; break; default: break; } } PurpleDebugUiOps Purple::debug_ops = { Purple::debug, NULL, //finch_debug_is_enabled, /* padding */ NULL, NULL, NULL, NULL }; void Purple::debug_init() { purple_debug_set_ui_ops(&debug_ops); } void Purple::minbif_prefs_init() { purple_prefs_add_none("/minbif"); purple_prefs_add_string("/minbif/password", ""); purple_prefs_add_int("/minbif/typing_notice", 0); purple_prefs_add_bool("/minbif/accept_nobuddies_messages", true); purple_prefs_add_int("/minbif/send_delay", 300); purple_prefs_add_bool("/minbif/voiced_buddies", true); purple_prefs_add_bool("/minbif/server_aliases", true); } GHashTable *Purple::ui_info = NULL; GHashTable *Purple::minbif_ui_get_info(void) { if (ui_info == NULL) { ui_info = g_hash_table_new(g_str_hash, g_str_equal); g_hash_table_insert(ui_info, (void*)"name", (void*)MINBIF_VERSION_NAME); g_hash_table_insert(ui_info, (void*)"version", (void*)MINBIF_VERSION); g_hash_table_insert(ui_info, (void*)"website", (void*)MINBIF_WEBSITE); g_hash_table_insert(ui_info, (void*)"dev_website", (void*)MINBIF_DEV_WEBSITE); g_hash_table_insert(ui_info, (void*)"client_type", (void*)"pc"); } return ui_info; } PurpleCoreUiOps Purple::core_ops = { Purple::minbif_prefs_init, Purple::debug_init, Purple::inited, Purple::uninit, Purple::minbif_ui_get_info, /* padding */ NULL, NULL, NULL }; void Purple::init(IM* im) { if(Purple::im) throw PurpleError("These is already a purple instance!"); purple_util_set_user_dir(im->getUserPath().c_str()); purple_core_set_ui_ops(&core_ops); purple_eventloop_set_ui_ops(&eventloop_ops); Purple::im = im; if (!purple_core_init(MINBIF_VERSION_NAME)) throw PurpleError("Initialization of the Purple core failed."); /* XXX the currently implementation of this function works only with * dbus, but minbif does not use it. */ if (!purple_core_ensure_single_instance()) throw PurpleError("You are already connected on this account with minbif."); purple_set_blist(purple_blist_new()); purple_blist_load(); } void Purple::inited() { Account::init(); RoomList::init(); Buddy::init(); Conversation::init(); Request::init(); FileTransfert::init(); Media::init(); irc::IRC* irc = getIM()->getIRC(); irc::BuddyIcon* bi = new irc::BuddyIcon(getIM(), irc); irc->addNick(bi); bool conv_logs = conf.GetSection("logging")->GetItem("conv_logs")->Boolean(); purple_prefs_set_bool("/purple/logging/log_ims", conv_logs); purple_prefs_set_bool("/purple/logging/log_chats", conv_logs); purple_prefs_set_bool("/purple/logging/log_system", conv_logs); } void Purple::uninit() { assert(im != NULL); if(ui_info) g_hash_table_destroy(ui_info); Account::uninit(); RoomList::uninit(); Buddy::uninit(); Conversation::uninit(); Request::uninit(); FileTransfert::uninit(); Media::uninit(); } map Purple::getPluginsList() { map m; GList* list; purple_plugins_probe(G_MODULE_SUFFIX); for(list = purple_plugins_get_all(); list; list = list->next) { PurplePlugin* plugin = (PurplePlugin*)list->data; if (plugin->info->type == PURPLE_PLUGIN_LOADER) { GList *cur; for (cur = PURPLE_PLUGIN_LOADER_INFO(plugin)->exts; cur != NULL; cur = cur->next) purple_plugins_probe((const char*)cur->data); continue; } if (plugin->info->type != PURPLE_PLUGIN_STANDARD || plugin->info->flags & PURPLE_PLUGIN_FLAG_INVISIBLE) continue; m[plugin->info->id] = Plugin(plugin); } return m; } map Purple::getProtocolsList() { map m; GList* list = purple_plugins_get_protocols(); for(; list; list = list->next) { Protocol protocol = Protocol((PurplePlugin*)list->data); m[protocol.getID()] = protocol; } return m; } map Purple::getAccountsList() { map m; GList* list = purple_accounts_get_all(); map protocols = getProtocolsList(); for(; list; list = list->next) { Protocol proto = getProtocolByPurpleID(((PurpleAccount*)list->data)->protocol_id); if(!proto.isValid()) continue; Account account = Account((PurpleAccount*)list->data, proto); m[account.getID()] = account; } return m; } Protocol Purple::getProtocolByPurpleID(string id) { GList* list = purple_plugins_get_protocols(); for(; list; list = list->next) if(id == ((PurplePlugin*)list->data)->info->id) return Protocol((PurplePlugin*)list->data); return Protocol(); } string Purple::getNewAccountName(Protocol proto, const Account& butone) { GList* list = purple_accounts_get_all(); GList* iter; int i = 0; for(iter = list; iter; iter = (iter ? iter->next : list)) { Account acc((PurpleAccount*)iter->data); if(acc == butone || !acc.getProtocol().isValid() || acc.getProtocol() != proto) continue; string id = acc.getID(false); if(id == proto.getID() + (i ? t2s(i) : string())) { if (i) i = s2t(id.substr(proto.getID().size())) + 1; else i = 1; iter = NULL; // restart } } return proto.getID() + (i ? t2s(i) : string()); } Account Purple::addAccount(const Protocol& proto, const string& username, const Protocol::Options& options, bool register_account) { PurpleAccount *account = purple_account_new(username.c_str(), proto.getPurpleID().c_str()); Account a(account); try { a.setOptions(options); } catch(Protocol::OptionError& e) { purple_account_destroy(account); throw; } if (register_account) a.registerAccount(); purple_accounts_add(account); const PurpleSavedStatus *saved_status; saved_status = purple_savedstatus_get_current(); if (saved_status != NULL) { purple_savedstatus_activate_for_account(saved_status, account); purple_account_set_enabled(account, MINBIF_VERSION_NAME, TRUE); } return a; } void Purple::delAccount(PurpleAccount* account) { purple_request_close_with_handle(account); /* Close any other opened delete window */ purple_accounts_delete(account); } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/roomlist.cpp0000644000175000017500000001041511754151773016252 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "im/roomlist.h" #include "im/conversation.h" #include "im/purple.h" #include "im/im.h" #include "irc/irc.h" #include "irc/user.h" #include "core/log.h" #include "core/util.h" namespace im { /* METHODS */ RoomList::RoomList(PurpleRoomlist* rl) : rlist(rl) { } RoomList::~RoomList() { } void RoomList::create(PurpleRoomlist* list) { } void RoomList::set_fields(PurpleRoomlist* list, GList* fields) { GList* l; string str; for(l = fields; l; l = l->next) { PurpleRoomlistField *f = (PurpleRoomlistField*)l->data; if(!f->hidden) str += string(f->label) + " "; } irc::IRC* irc = Purple::getIM()->getIRC(); irc->getUser()->send(irc::Message(RPL_LISTSTART).setSender(irc) .setReceiver(irc->getUser()) .addArg("Channel") .addArg(str)); } void RoomList::add_room(PurpleRoomlist* list, PurpleRoomlistRoom* room) { PurpleConnection *gc = purple_account_get_connection(list->account); if(!gc) return; char* name; string str; PurplePluginProtocolInfo* prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)); if(prpl_info != NULL && prpl_info->roomlist_room_serialize) name = prpl_info->roomlist_room_serialize(room); else name = g_strdup(purple_roomlist_room_get_name(room)); GList *iter, *field; for (iter = purple_roomlist_room_get_fields(room), field = purple_roomlist_get_fields(list); iter && field; iter = iter->next, field = field->next) { PurpleRoomlistField *f = (PurpleRoomlistField*)field->data; if (purple_roomlist_field_get_hidden(f)) continue; switch (purple_roomlist_field_get_type(f)) { case PURPLE_ROOMLIST_FIELD_BOOL: str += iter->data ? "True " : "False "; break; case PURPLE_ROOMLIST_FIELD_INT: str += t2s((size_t)iter->data) + " "; break; case PURPLE_ROOMLIST_FIELD_STRING: str += string((const char*)iter->data) + " "; break; } } irc::IRC* irc = Purple::getIM()->getIRC(); irc->getUser()->send(irc::Message(RPL_LIST).setSender(irc) .setReceiver(irc->getUser()) .addArg(Conversation::normalizeIRCName(name, Account(list->account))) .addArg(str)); g_free(name); } void RoomList::in_progress(PurpleRoomlist* list, gboolean flag) { if(!flag) { irc::IRC* irc = Purple::getIM()->getIRC(); irc->getUser()->send(irc::Message(RPL_LISTEND).setSender(irc) .setReceiver(irc->getUser()) .addArg("End of /LIST")); } } void RoomList::destroy(PurpleRoomlist* list) { } /* STATICS */ PurpleRoomlistUiOps RoomList::ui_ops = { NULL, /* void (*show_with_account)(PurpleAccount *account); **< Force the ui to pop up a dialog and get the list */ &RoomList::create, /* void (*create)(PurpleRoomlist *list); **< A new list was created. */ &RoomList::set_fields, /* void (*set_fields)(PurpleRoomlist *list, GList *fields); **< Sets the columns. */ &RoomList::add_room, /* void (*add_room)(PurpleRoomlist *list, PurpleRoomlistRoom *room); **< Add a room to the list. */ &RoomList::in_progress, /* void (*in_progress)(PurpleRoomlist *list, gboolean flag); **< Are we fetching stuff still? */ &RoomList::destroy, /* void (*destroy)(PurpleRoomlist *list); **< We're destroying list. */ NULL, /* void (*_purple_reserved1)(void); */ NULL, /* void (*_purple_reserved2)(void); */ NULL, /* void (*_purple_reserved3)(void); */ NULL /* void (*_purple_reserved4)(void); */ }; void RoomList::init() { purple_roomlist_set_ui_ops(&ui_ops); } void RoomList::uninit() { purple_roomlist_set_ui_ops(NULL); } } /* ns im */ minbif-1.0.5+git20120508/src/im/media.cpp0000644000175000017500000002537711754151773015476 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "im/media.h" #include "im/im.h" #include "im/purple.h" #include "irc/irc.h" #include "irc/dcc.h" #include "irc/user.h" #include "irc/buddy.h" #include "core/log.h" #include "core/caca_image.h" #include "core/util.h" #include "core/callback.h" namespace im { #ifdef HAVE_VIDEO MediaList::MediaList() : Mutex(), check_cb(NULL), check_id(-1) { } MediaList::~MediaList() { if(check_id >= 0) g_source_remove(check_id); delete check_cb; } void MediaList::addMedia(const Media& media) { BlockLockMutex m(this); if(check_id < 0) { check_cb = new CallBack(this, &MediaList::check); check_id = g_timeout_add(50, g_callback, check_cb); } medias.push_back(media); } Media MediaList::getMedia(PurpleMedia* m) { BlockLockMutex lock(this); vector::iterator it; for(it = medias.begin(); it != medias.end() && it->getPurpleMedia() != m; ++it) ; if(it == medias.end()) return Media(); else return *it; } void MediaList::removeMedia(const Media& media) { BlockLockMutex m(this); vector::iterator it; for(it = medias.begin(); it != medias.end(); ) if(*it == media) it = medias.erase(it); else ++it; } void MediaList::enqueueBuffer(const Media& media, const CacaImage& buf) { BlockLockMutex m(this); vector::iterator it; for(it = medias.begin(); it != medias.end() && *it != media; ++it) ; if(it == medias.end()) { b_log[W_WARNING] << "Trying to enqueue data to an unknown stream."; return; } it->enqueueBuffer(buf); } bool MediaList::check(void*) { BlockLockMutex m(this); vector::iterator it; for(it = medias.begin(); it != medias.end(); ++it) it->checkBuffer(); return true; } Media::Media() : media(0), dcc(NULL) { } Media::Media(PurpleMedia* m) : media(m), dcc(NULL) {} Media::Media(PurpleMedia* m, const Buddy& b) : media(m), buddy(b), dcc(NULL) { } Media::Media(const Media& m) : media(m.media), buddy(m.buddy), dcc(NULL) { } Media& Media::operator=(const Media& m) { media = m.media; buddy = m.buddy; delete dcc; dcc = NULL; return *this; } Media::~Media() { delete dcc; } bool Media::operator==(const Media& m) const { return this->media && this->media == m.media; } bool Media::operator!=(const Media& m) const { return !this->media || this->media != m.media; } void Media::enqueueBuffer(const CacaImage& buf) { queue.push_back(buf); } void Media::checkBuffer() { vector::iterator it; for(it = queue.begin(); it != queue.end(); it = queue.erase(it)) { try { if(!dcc) { irc::IRC* irc = Purple::getIM()->getIRC(); irc::Buddy* sender = irc->getNick(buddy); dcc = new irc::DCCChat(sender, irc->getUser()); } string buf = it->getIRCBuffer(0, 20, "ansi"), line; dcc->dcc_send(buf); } catch(CacaError &e) { b_log[W_ERR] << "Caca error while sending to user"; } } } #endif /* HAVE_VIDEO */ /* STATIC */ void Media::init() { #ifdef HAVE_VIDEO GError *error = NULL; if((gstreamer_init_failed = !gst_init_check(NULL, NULL, &error))) { b_log[W_ERR] << "Unable to initialize GStreamer: " << (error ? error->message : ""); if(error) g_free(error); return; } PurpleMediaManager *manager = purple_media_manager_get(); PurpleMediaElementInfo *default_video_src = (PurpleMediaElementInfo*)g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, "id", "minbifdefaultvideosrc", "name", "minbif Default Video Source", "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC | PURPLE_MEDIA_ELEMENT_ONE_SRC | PURPLE_MEDIA_ELEMENT_UNIQUE, "create-cb", create_default_video_src, NULL); PurpleMediaElementInfo *default_video_sink = (PurpleMediaElementInfo*)g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO, "id", "minbifdefaultvideosink", "name", "minbif Default Video Sink", "type", PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK | PURPLE_MEDIA_ELEMENT_ONE_SINK, "create-cb", create_default_video_sink, NULL); g_signal_connect(G_OBJECT(manager), "init-media", G_CALLBACK(media_new_cb), NULL); purple_media_manager_set_ui_caps(manager, (PurpleMediaCaps)( PURPLE_MEDIA_CAPS_VIDEO | PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION)); purple_media_manager_set_active_element(manager, default_video_sink); purple_media_manager_set_active_element(manager, default_video_src); #endif /* HAVE_VIDEO */ } void Media::uninit() { #ifdef HAVE_VIDEO if(!gstreamer_init_failed) gst_deinit(); #endif /* HAVE_VIDEO */ } #ifdef HAVE_VIDEO MediaList Media::media_list; bool Media::gstreamer_init_failed = true; static void minbif_media_accept_cb(PurpleMedia *media, int index) { purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT, NULL, NULL, TRUE); } static void minbif_media_reject_cb(PurpleMedia *media, int index) { purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT, NULL, NULL, TRUE); } gboolean Media::minbif_media_ready_cb(PurpleMedia *media) { Media m = media_list.getMedia(media); string alias = m.getBuddy().getAlias(); //PurpleMediaSessionType type = purple_media_get_session_type(media, sid); PurpleMediaSessionType type = PURPLE_MEDIA_VIDEO; gchar *message = NULL; PurpleAccount* account = purple_media_get_account(media); if (type & PURPLE_MEDIA_AUDIO && type & PURPLE_MEDIA_VIDEO) { message = g_strdup_printf("%s wishes to start an audio/video session with you.", alias.c_str()); } else if (type & PURPLE_MEDIA_AUDIO) { message = g_strdup_printf("%s wishes to start an audio session with you.", alias.c_str()); } else if (type & PURPLE_MEDIA_VIDEO) { message = g_strdup_printf("%s wishes to start a video session with you.", alias.c_str()); } /* purple_request_accept_cancel is a macro and calls _() to translates * buttons strings. * There isn't (yet?) any translation system in Minbif, so the _ macro * is defined to make minbif compiles. */ #define _ purple_request_accept_cancel(media, "Incoming Call", message, NULL, PURPLE_DEFAULT_ACTION_NONE, account, alias.c_str(), NULL, media, minbif_media_accept_cb, minbif_media_reject_cb); #undef _ g_free(message); return FALSE; } static void minbif_media_error_cb(PurpleMedia *media, const char *error, void *gtkmedia) { b_log[W_ERR] << "Error: " << error; } void Media::minbif_media_state_changed_cb(PurpleMedia *media, PurpleMediaState state, gchar *sid, gchar *name, void* gtkmedia) { if (sid == NULL && name == NULL) { if (state == PURPLE_MEDIA_STATE_END) { media_list.removeMedia(Media(media)); b_log[W_INFO] << "The call has been terminated."; } } else if (state == PURPLE_MEDIA_STATE_NEW && sid != NULL && name != NULL) { if (purple_media_is_initiator(media, sid, NULL) == FALSE) { g_timeout_add(500, (GSourceFunc)Media::minbif_media_ready_cb, gtkmedia); purple_media_set_output_window(media, sid, name, 0); } } } static void minbif_media_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type, gchar *sid, gchar *name, gboolean local, void *gtkmedia) { if (type == PURPLE_MEDIA_INFO_REJECT) { b_log[W_INFO] << "You have rejected the call."; } else if (type == PURPLE_MEDIA_INFO_ACCEPT) { b_log[W_INFO] << "Call in progress"; } } gboolean Media::media_new_cb(PurpleMediaManager *manager, PurpleMedia *media, PurpleAccount *account, gchar *screenname, gpointer nul) { PurpleBuddy *buddy = purple_find_buddy(account, screenname); const gchar *alias = buddy ? purple_buddy_get_contact_alias(buddy) : screenname; b_log[W_INFO] << "Started video with " << alias; media_list.addMedia(Media(media, Buddy(buddy))); g_signal_connect(G_OBJECT(media), "error", G_CALLBACK(minbif_media_error_cb), media); g_signal_connect(G_OBJECT(media), "state-changed", G_CALLBACK(minbif_media_state_changed_cb), media); g_signal_connect(G_OBJECT(media), "stream-info", G_CALLBACK(minbif_media_stream_info_cb), media); return TRUE; } GstElement *Media::create_default_video_src(PurpleMedia *media, const gchar *session_id, const gchar *participant) { GstElement *sendbin, *src, *videoscale, *capsfilter; GstPad *pad; GstPad *ghost; GstCaps *caps; src = gst_element_factory_make("videotestsrc", NULL); if (src == NULL) { purple_debug_error("gtkmedia", "Unable to find a suitable " "element for the default video source.\n"); return NULL; } g_object_set (G_OBJECT (src), "is-live", true, NULL); sendbin = gst_bin_new("minbifdefaultvideosrc"); videoscale = gst_element_factory_make("videoscale", NULL); capsfilter = gst_element_factory_make("capsfilter", NULL); /* It was recommended to set the size <= 352x288 and framerate <= 20 */ caps = gst_caps_from_string("video/x-raw-yuv , width=[250,352] , " "height=[200,288] , framerate=[1/1,20/1]"); g_object_set(G_OBJECT(capsfilter), "caps", caps, NULL); gst_bin_add_many(GST_BIN(sendbin), src, videoscale, capsfilter, NULL); gst_element_link_many(src, videoscale, capsfilter, NULL); pad = gst_element_get_static_pad(capsfilter, "src"); ghost = gst_ghost_pad_new("ghostsrc", pad); gst_object_unref(pad); gst_element_add_pad(sendbin, ghost); return sendbin; } void Media::got_data(GstElement* object, GstBuffer* buffer, GstPad* arg1, gpointer user_data) { try { gint w, h; guint bpp = 0; GstStructure* structure = gst_caps_get_structure (buffer->caps, 0); gst_structure_get_int (structure, "width", &w); gst_structure_get_int (structure, "height", &h); gst_structure_get_int (structure, "bpp", (int *) &bpp); if(!bpp) bpp = 8; CacaImage img(GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer), w, h, bpp); Media::media_list.enqueueBuffer(Media((PurpleMedia*)user_data), img); } catch(CacaError& e) { } } GstElement *Media::create_default_video_sink(PurpleMedia *media, const gchar *session_id, const gchar *participant) { GstElement *ffmpegcolorspace, *fakesink; ffmpegcolorspace = gst_element_factory_make("ffmpegcolorspace", NULL); fakesink = gst_element_factory_make("fakesink", NULL); g_object_set(G_OBJECT(fakesink), "signal-handoffs", TRUE, NULL); g_signal_connect(fakesink, "handoff", G_CALLBACK(got_data), media); gst_element_link_many(ffmpegcolorspace, fakesink, NULL); return fakesink; } #endif /* HAVE_VIDEO */ } /* ns im */ minbif-1.0.5+git20120508/src/im/auth_pam.h0000644000175000017500000000305011754151773015642 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IM_AUTH_PAM_H #define IM_AUTH_PAM_H #include "auth.h" #include #include struct _pam_conv_func_data { bool update; string password; string new_password; }; /** IM related classes */ namespace im { using std::string; class AuthPAM : public Auth { public: AuthPAM(irc::IRC* _irc, const string& _username); ~AuthPAM(); bool exists(); bool authenticate(const string& password); im::IM* create(const string& password); bool setPassword(const string& password); string getPassword() const; private: pam_handle_t *pamh; struct pam_conv pam_conversation; struct _pam_conv_func_data pam_conv_func_data; void close(int retval = PAM_SUCCESS); bool checkPassword(const string& password); }; }; #endif /* IM_AUTH_PAM_H */ minbif-1.0.5+git20120508/src/im/conversation.cpp0000644000175000017500000004743111754151773017124 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "im/conversation.h" #include "im/purple.h" #include "im/buddy.h" #include "im/im.h" #include "irc/irc.h" #include "irc/user.h" #include "irc/message.h" #include "irc/conversation_channel.h" #include "irc/buddy.h" #include "irc/chat_buddy.h" #include "irc/unknown_buddy.h" #include "core/log.h" namespace im { /***** * ChatBuddy */ ChatBuddy::ChatBuddy(Conversation _conv, PurpleConvChatBuddy* _cbuddy) : conv(_conv), cbuddy(_cbuddy) {} ChatBuddy::ChatBuddy() : cbuddy(NULL) {} string ChatBuddy::getName() const { assert(isValid()); return cbuddy->name ? cbuddy->name : ""; } string ChatBuddy::getAlias() const { assert(isValid()); return cbuddy->alias ? cbuddy->alias : getName(); } string ChatBuddy::getRealName() const { assert(isValid()); PurplePluginProtocolInfo* prpl = conv.getAccount().getProtocol().getPurpleProtocol(); if(prpl->get_cb_real_name) { gchar* tmp = prpl->get_cb_real_name(conv.getAccount().getPurpleConnection(), conv.getPurpleChat()->id, cbuddy->name); string realname = tmp; g_free(tmp); return realname; } else return getName(); } int ChatBuddy::getChanStatus() const { assert(isValid()); return cbuddy->flags; } Account ChatBuddy::getAccount() const { assert(isValid()); return conv.getAccount(); } bool ChatBuddy::isMe() const { assert(isValid()); return getName() == purple_conv_chat_get_nick(conv.getPurpleChat()); } bool ChatBuddy::operator==(const ChatBuddy& cbuddy) const { return this->cbuddy == cbuddy.cbuddy; } bool ChatBuddy::operator!=(const ChatBuddy& cbuddy) const { return this->cbuddy != cbuddy.cbuddy; } bool ChatBuddy::operator<(const ChatBuddy& cbuddy) const { return this->cbuddy < cbuddy.cbuddy; } /**** * Conversation */ Conversation::Conversation() : conv(NULL) {} Conversation::Conversation(PurpleConversation* _conv) : conv(_conv) {} Conversation::Conversation(const Account& account, const Buddy& buddy) : conv(NULL) { conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account.getPurpleAccount(), buddy.getName().c_str()); } Conversation::Conversation(const Account& account, const ChatBuddy& cbuddy) : conv(NULL) { conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account.getPurpleAccount(), cbuddy.getRealName().c_str()); } Conversation::Conversation(const Account& account, const string& name) : conv(NULL) { conv = purple_conversation_new(PURPLE_CONV_TYPE_CHAT, account.getPurpleAccount(), name.c_str()); } bool Conversation::operator==(const Conversation& conv) const { return conv.conv == this->conv; } bool Conversation::operator!=(const Conversation& conv) const { return conv.conv != this->conv; } Account Conversation::getAccount() const { assert(isValid()); return Account(conv->account); } string Conversation::getName() const { assert(isValid()); const char* name = conv->name; if(name) return name; else return ""; } string Conversation::normalizeIRCName(string name, const Account& acc) { for(string::iterator it = name.begin(); it != name.end(); ++it) if(*it == ' ') *it = '_'; string n = "#" + name + ":" + acc.getID(); return n; } string Conversation::getChanName() const { assert(isValid()); return normalizeIRCName(getName(), getAccount()); } string Conversation::getChanTopic() const { assert(isValid()); const char* topic = purple_conv_chat_get_topic(getPurpleChat()); if(topic) return topic; else return ""; } PurpleConversationType Conversation::getType() const { assert(isValid()); return purple_conversation_get_type(conv); } void Conversation::setNick(irc::ConvNick* n, bool purge_unknown) { assert(isValid()); assert(getType() == PURPLE_CONV_TYPE_IM); irc::ConvNick* last = getNick(); conv->ui_data = n; if(last && last != n) { if(purge_unknown && dynamic_cast(last)) { irc::IRC* irc = Purple::getIM()->getIRC(); irc->removeNick(last->getNickname()); } else last->setConversation(Conversation()); } } irc::ConvNick* Conversation::getNick() const { assert(isValid()); assert(getType() == PURPLE_CONV_TYPE_IM); return static_cast(conv->ui_data); } void Conversation::setChannel(irc::ConversationChannel* c) const { assert(isValid()); assert(getType() == PURPLE_CONV_TYPE_CHAT); conv->ui_data = c; } irc::ConversationChannel* Conversation::getChannel() const { assert(isValid()); assert(getType() == PURPLE_CONV_TYPE_CHAT); return static_cast(conv->ui_data); } void Conversation::present() const { assert(isValid()); purple_conversation_present(conv); } void Conversation::leave() { assert(isValid()); /* As this method can be called by ConversationChannel, which owns me, * it isn't a good idea to change myself after call of * purple_conversation_destroy(). */ PurpleConversation* c = conv; conv = NULL; purple_conversation_destroy(c); } void Conversation::invite(const string& buddy, const string& message) { assert(isValid()); serv_chat_invite(purple_conversation_get_gc(conv), purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), message.c_str(), buddy.c_str()); } bool Conversation::setTopic(const string& topic) { assert(isValid()); assert(getType() == PURPLE_CONV_TYPE_CHAT); PurplePluginProtocolInfo *prpl_info = NULL; PurpleConnection *gc; gc = purple_conversation_get_gc(conv); if(!gc || !(prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl))) return false; if(prpl_info->set_chat_topic == NULL) return false; char *utf8 = purple_utf8_try_convert(topic.c_str()); prpl_info->set_chat_topic(gc, purple_conv_chat_get_id(getPurpleChat()), utf8); g_free(utf8); return true; } void Conversation::createChannel() const { assert(isValid()); assert(getType() == PURPLE_CONV_TYPE_CHAT); irc::IRC* irc = Purple::getIM()->getIRC(); irc::ConversationChannel* chan = new irc::ConversationChannel(irc, *this); setChannel(chan); while(irc->getChannel(chan->getName())) chan->setName(chan->getName() + "_"); irc->addChannel(chan); /* XXX As callback add_users will be called and I'll be in added users, * a better idea could be do not join here, but wait for the add_users * where i'll be. * BUT, there is a bug with the jabber plugin, so it is possible this * event will never happen. */ irc->getUser()->join(chan); GList* l = purple_conv_chat_get_users(getPurpleChat()); for (; l != NULL; l = l->next) { ChatBuddy cbuddy = ChatBuddy(conv, (PurpleConvChatBuddy *)l->data); b_log[W_DEBUG] << cbuddy.getName(); } } void Conversation::destroyChannel() const { assert(isValid()); assert(getType() == PURPLE_CONV_TYPE_CHAT); irc::IRC* irc = Purple::getIM()->getIRC(); irc->removeChannel(getChannel()->getName()); setChannel(NULL); } PurpleConvChat* Conversation::getPurpleChat() const { assert(isValid()); return PURPLE_CONV_CHAT(conv); } PurpleConvIm* Conversation::getPurpleIm() const { assert(isValid()); return PURPLE_CONV_IM(conv); } int Conversation::sendCommand(const string& cmd) const { char* error = NULL; gchar* escape = g_markup_escape_text(cmd.c_str(), -1); PurpleCmdStatus ret = purple_cmd_do_command(conv, cmd.c_str(), escape, &error); g_free(escape); return ret; } void Conversation::sendMessage(string text) { assert(isValid()); if(text.find("\001ACTION ") == 0) { /* Check if this protocol has registered the /me command. */ text = "/me " + text.substr(8, text.size() - 8 - 1); char *utf8 = purple_utf8_try_convert(text.c_str() + 1); char *escape = g_markup_escape_text(utf8, -1); char* error = NULL; int status = purple_cmd_do_command(conv, utf8, escape, &error); g_free(error); g_free(escape); g_free(utf8); if(status == PURPLE_CMD_STATUS_OK) return; /* If the /me command is not implemented for this protocol, just * continue to send message prefixed with /me. */ } if(text.find("\001TYPING ") == 0) { string typing = text.substr(8, text.size() - 8 - 1); int timeout; if (typing == "1") { timeout = serv_send_typing(purple_conversation_get_gc(conv), purple_conversation_get_name(conv), PURPLE_TYPING); purple_conv_im_set_type_again(getPurpleIm(), timeout); } else if (typing == "0") { purple_conv_im_stop_send_typed_timeout(getPurpleIm()); timeout = serv_send_typing(purple_conversation_get_gc(conv), purple_conversation_get_name(conv), PURPLE_NOT_TYPING); } return; } char *utf8 = purple_utf8_try_convert(text.c_str()); char *escape = irc2markup(utf8); char *apos = purple_strreplace(escape, "'", "'"); g_free(escape); escape = apos; switch(getType()) { case PURPLE_CONV_TYPE_IM: purple_conv_im_send_with_flags(getPurpleIm(), escape, PURPLE_MESSAGE_SEND); break; case PURPLE_CONV_TYPE_CHAT: purple_conv_chat_send(getPurpleChat(), escape); break; default: break; } g_free(escape); g_free(utf8); purple_idle_touch(); } void Conversation::recvMessage(string from, string text, bool action) { assert(isValid()); irc::IRC* irc = Purple::getIM()->getIRC(); switch(getType()) { case PURPLE_CONV_TYPE_IM: { irc::ConvNick* n = getNick(); if(!n) { /* There isn't any Nick associated to this conversation. Try to * find a Nick with this conversation object. */ n = dynamic_cast(irc->getNick(*this)); if(!n) { /* If there isn't any conversation, try to find a buddy. */ n = dynamic_cast(irc->getNick(from)); if(!n) { if (!Purple::getIM()->hasAcceptNoBuddiesMessages()) return; /* Ok, there isn't any buddy, so I create an unknown buddy to chat with him. */ n = new irc::UnknownBuddy(irc->getServer(getAccount().getServername()), *this); while((irc->getNick(n->getNickname()))) n->setNickname(n->getNickname() + "_"); irc->addNick(n); } } setNick(n); n->setConversation(*this); } string line; while((line = stringtok(text, "\n\r")).empty() == false) n->sendMessage(irc->getUser(), line, action); break; } case PURPLE_CONV_TYPE_CHAT: { irc::ConversationChannel* chan = getChannel(); if(!chan) { b_log[W_WARNING] << "Received message in unknown chat " << getChanName() << ": " << text; return; } irc::ChanUser* n = chan->getChanUser(from); /* This is an unknown buddy, but the message is displayed, even if there * isn't any IRC user linked to this buddy. * So I avoid a conflict between a real IRC user with the same nick. */ if(!n) { while((irc->getNick(from))) from += "_"; } string line; while((line = stringtok(text, "\n\r")).empty() == false) { if(action) line = "\001ACTION " + line + "\001"; if(n) chan->broadcast(irc::Message(MSG_PRIVMSG).setSender(n) .setReceiver(chan) .addArg(line)); else chan->broadcast(irc::Message(MSG_PRIVMSG).setSender(from) .setReceiver(chan) .addArg(line)); } break; } default: break; } } /* STATIC */ PurpleConversationUiOps Conversation::conv_ui_ops = { Conversation::create, NULL,//finch_destroy (use signal instead) NULL,//finch_write_chat, Conversation::write_im, Conversation::write_conv, Conversation::add_users, Conversation::chat_rename_user, NULL,//Conversation::remove_users, use signal instead Conversation::update_user, Conversation::conv_present,//finch_conv_present, /* present */ NULL,//finch_conv_has_focus, /* has_focus */ NULL, /* custom_smiley_add */ NULL, /* custom_smiley_write */ NULL, /* custom_smiley_close */ NULL, /* send_confirm */ NULL, NULL, NULL, NULL }; void Conversation::init() { purple_conversations_set_ui_ops(&conv_ui_ops); purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation", getHandler(), PURPLE_CALLBACK(destroy), NULL); purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed", getHandler(), PURPLE_CALLBACK(topic_changed), NULL); purple_signal_connect(purple_conversations_get_handle(), "chat-buddy-leaving", getHandler(), PURPLE_CALLBACK(remove_user), NULL); purple_signal_connect(purple_conversations_get_handle(), "buddy-typing", getHandler(), PURPLE_CALLBACK(buddy_typing), NULL); purple_signal_connect(purple_conversations_get_handle(), "buddy-typing-stopped", getHandler(), PURPLE_CALLBACK(buddy_typing), NULL); } void Conversation::uninit() { purple_conversations_set_ui_ops(NULL); purple_signals_disconnect_by_handle(getHandler()); } void* Conversation::getHandler() { static int handler; return &handler; } void Conversation::create(PurpleConversation* c) { Conversation conv(c); switch(conv.getType()) { case PURPLE_CONV_TYPE_IM: break; case PURPLE_CONV_TYPE_CHAT: conv.createChannel(); break; default: break; } } void Conversation::destroy(PurpleConversation* c) { Conversation conv(c); switch(conv.getType()) { case PURPLE_CONV_TYPE_IM: { conv.setNick(NULL); irc::IRC* irc = Purple::getIM()->getIRC(); irc::ConvNick* n = dynamic_cast(irc->getNick(conv)); if(n) n->setConversation(Conversation()); break; } case PURPLE_CONV_TYPE_CHAT: conv.destroyChannel(); break; default: break; } } void Conversation::conv_present(PurpleConversation* c) { } void Conversation::write_im(PurpleConversation *c, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime) { if(flags & PURPLE_MESSAGE_RECV) { PurpleAccount *account = purple_conversation_get_account(c); PurpleBuddy *buddy = purple_find_buddy(account, purple_conversation_get_name(c)); if (buddy) who = purple_buddy_get_contact_alias(buddy); } purple_conversation_write(c, who, message, flags, mtime); } void Conversation::write_conv(PurpleConversation *c, const char *who, const char* alias, const char *message, PurpleMessageFlags flags, time_t mtime) { if ((flags & PURPLE_MESSAGE_SYSTEM) && !(flags & PURPLE_MESSAGE_NOTIFY)) flags = (PurpleMessageFlags)(flags & ~(PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)); if(flags & PURPLE_MESSAGE_RECV) { Conversation conv = Conversation(c); bool action = false; if(who && purple_message_meify((char*)message, -1)) action = true; char* strip = markup2irc(message); string from; if(alias && *alias) from = alias; else if(who && *who) from = who; if(flags & PURPLE_MESSAGE_DELAYED) { struct tm lt; struct tm today; time_t now = time(NULL); char* msg; localtime_r(&mtime, <); localtime_r(&now, &today); if (lt.tm_mday != today.tm_mday || lt.tm_mon != today.tm_mon || lt.tm_year != today.tm_year) msg = g_strdup_printf("[\002%04d-%02d-%02d@%02d:%02d:%02d\002] %s", lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec, strip); else msg = g_strdup_printf("[\002%02d:%02d:%02d\002] %s", lt.tm_hour, lt.tm_min, lt.tm_sec, strip); conv.recvMessage(from, msg, action); g_free(msg); } else conv.recvMessage(from, strip ? strip : "", action); g_free(strip); } } void Conversation::add_users(PurpleConversation *c, GList *cbuddies, gboolean new_arrivals) { Conversation conv(c); GList* l = cbuddies; for (; l != NULL; l = l->next) { ChatBuddy cbuddy = ChatBuddy(conv, (PurpleConvChatBuddy *)l->data); irc::ConversationChannel* chan = conv.getChannel(); if(!chan) { b_log[W_ERR] << "Conversation channel doesn't exist: " << conv.getChanName(); return; } chan->addBuddy(cbuddy, cbuddy.getChanStatus()); } } void Conversation::update_user(PurpleConversation* c, const char* user) { Conversation conv(c); ChatBuddy cbuddy(conv, purple_conv_chat_cb_find(conv.getPurpleChat(), user)); if(!cbuddy.isValid()) { b_log[W_DESYNCH] << "Update channel user " << user << " who is not found"; return; } irc::ConversationChannel* chan = conv.getChannel(); if(!chan) { b_log[W_DESYNCH] << "Conversation channel doesn't exist: " << conv.getChanName(); return; } chan->updateBuddy(cbuddy); } void Conversation::remove_user(PurpleConversation* c, const char* cbname, const char *reason) { Conversation conv(c); irc::ConversationChannel* chan = conv.getChannel(); if(!chan) { b_log[W_ERR] << "Conversation channel doesn't exist: " << conv.getChanName(); return; } irc::ChanUser* cu = chan->getChanUser(cbname); if(!cu) { b_log[W_ERR] << cbname << " tries to leave channel, but I don't know who is it!"; return; } irc::Nick* nick = cu->getNick(); nick->part(chan, reason ? reason : ""); } void Conversation::chat_rename_user(PurpleConversation *c, const char *old, const char *new_n, const char *new_a) { Conversation conv(c); ChatBuddy cbuddy(conv, purple_conv_chat_cb_find(conv.getPurpleChat(), old)); if(!cbuddy.isValid()) { b_log[W_ERR] << "Rename from " << old << " to " << new_n << " (" << new_a << ") which is an unknown chat buddy"; return; } irc::ConversationChannel* chan = conv.getChannel(); if(!chan) { b_log[W_ERR] << "Conversation channel doesn't exist: " << conv.getChanName(); return; } cbuddy = ChatBuddy(conv, purple_conv_chat_cb_find(conv.getPurpleChat(), new_n)); if(!cbuddy.isValid()) { b_log[W_ERR] << "New chat buddy " << new_n << " (" << new_a << ") unknown"; return; } irc::ChanUser* chanuser = chan->getChanUser(old); chan->renameBuddy(chanuser, cbuddy); } void Conversation::topic_changed(PurpleConversation* c, const char* who, const char* topic) { Conversation conv(c); irc::ConversationChannel* chan = conv.getChannel(); if(!chan) { b_log[W_ERR] << "Conversation channel doesn't exist: " << conv.getChanName(); return; } irc::ChanUser* chanuser = NULL; if(who) chanuser = chan->getChanUser(who); chan->setTopic(chanuser, topic ? topic : ""); } void Conversation::buddy_typing(PurpleAccount* account, const char* who, gpointer null) { if(!Purple::getIM()->hasTypingNotice()) return; Conversation conv(purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account)); if(!conv.isValid() || who == NULL) return; irc::IRC* irc = Purple::getIM()->getIRC(); irc::Nick* n = conv.getNick(); if(!n) n = irc->getNick(conv); if(!n) return; PurpleConvIm *im = conv.getPurpleIm(); switch(purple_conv_im_get_typing_state(im)) { case PURPLE_TYPING: irc->getUser()->send(irc::Message(MSG_PRIVMSG).setSender(n) .setReceiver(irc->getUser()) .addArg("\1TYPING 1\1")); break; case PURPLE_TYPED: irc->getUser()->send(irc::Message(MSG_PRIVMSG).setSender(n) .setReceiver(irc->getUser()) .addArg("\1TYPING 2\1")); break; case PURPLE_NOT_TYPING: irc->getUser()->send(irc::Message(MSG_PRIVMSG).setSender(n) .setReceiver(irc->getUser()) .addArg("\1TYPING 0\1")); break; } } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/request.cpp0000644000175000017500000005540111754151773016076 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "request.h" #include "buddy.h" #include "im.h" #include "purple.h" #include "irc/irc.h" #include "irc/user.h" #include "core/caca_image.h" #include "core/util.h" #include "core/log.h" #include "core/config.h" namespace im { class RequestNick : public irc::Nick { public: RequestNick(irc::Server* server, string nickname, string identname, string hostname, string realname="") : irc::Nick(server, nickname, identname, hostname, realname) {} void request_error(string msg) { irc::IRC* irc = Purple::getIM()->getIRC(); privmsg(irc->getUser(), msg); } void send(irc::Message m) { if(m.getCommand() != MSG_PRIVMSG || m.getReceiver() != this) return; string answer = m.getArg(0); if (answer[0] == '\001') return; irc::IRC* irc = Purple::getIM()->getIRC(); Request* request = Request::getFirstRequest(); if(!request) { privmsg(irc->getUser(), "No active question"); return; } try { request->process(answer); } catch(RequestFieldNotFound &e) { privmsg(irc->getUser(), "ERROR: Answer '" + answer + "' is not valid."); request->display(); return; } request->close(); } }; template class RequestFieldTmplt : public RequestField { int id; string label; string text; _CbT callback; void* data; _CbData2T data2; public: RequestFieldTmplt() : id(-1), callback(NULL), data(NULL) {} RequestFieldTmplt(int _id, string _label, string _text, _CbT _callback, void* _data, _CbData2T _data2) : id(_id), label(_label), text(_text), callback(_callback), data(_data), data2(_data2) {} virtual ~RequestFieldTmplt() {} void setLabel(const string& l) { label = l; } string getLabel() const { return label; } string getText() const { return text; } void runCallback() { if (callback) callback(data, data2); } }; template class RequestFieldAction : public RequestFieldTmplt<_CbT, int> { public: RequestFieldAction(int id, string label, string txt, _CbT callback, void* data) : RequestFieldTmplt<_CbT, int>(id, label, txt, callback, data, id) {} }; Request::Request(PurpleRequestType _type, const Account& _account, const string& _title, const string& _primary, const string& _secondary) : account(_account), title(_title), primary(_primary), secondary(_secondary), type(_type) { } RequestFieldList::~RequestFieldList() { map::iterator it; for(it = fields.begin(); it != fields.end(); ++it) delete it->second; for(vector::iterator r = subrequests.begin(); r != subrequests.end(); r = subrequests.erase(r)) delete *r; } void RequestFieldList::addField(RequestField* field) { while(fields.find(field->getLabel()) != fields.end()) field->setLabel(field->getLabel() + "_"); fields[field->getLabel()] = field; } RequestField* RequestFieldList::getField(const string& label) const { map::const_iterator it = fields.find(label); if(it == fields.end()) throw RequestFieldNotFound(); return it->second; } void RequestFieldList::addSubrequest(Request* request) { subrequests.push_back(request); } void RequestFieldList::process(const string& answer) const { if (!subrequests.empty()) subrequests.front()->process(answer); else { RequestField* field = getField(answer); field->runCallback(); } } void RequestFieldList::close() { if (!subrequests.empty()) { delete subrequests.front(); subrequests.erase(subrequests.begin()); display(); } else Request::close(); } void RequestFieldList::display() const { if (!subrequests.empty()) { subrequests.front()->display(); return; } irc::IRC* irc = Purple::getIM()->getIRC(); if(account.isValid()) nick->privmsg(irc->getUser(), "[" + account.getID() + "] New request: " + title); else nick->privmsg(irc->getUser(), "New request: " + title); if(primary.empty() == false) nick->privmsg(irc->getUser(), primary); if(secondary.empty() == false) nick->privmsg(irc->getUser(), secondary); for(map::const_iterator it = fields.begin(); it != fields.end(); ++it) { nick->privmsg(irc->getUser(), "- " + it->first + ": " + it->second->getText()); } } void RequestInput::process(const string& answer) const { callback(user_data, answer.c_str()); } void RequestInput::display() const { irc::IRC* irc = Purple::getIM()->getIRC(); if(account.isValid()) nick->privmsg(irc->getUser(), "[" + account.getID() + "] New request: " + title); else nick->privmsg(irc->getUser(), "New request: " + title); if(primary.empty() == false) nick->privmsg(irc->getUser(), primary); if(secondary.empty() == false) nick->privmsg(irc->getUser(), secondary); nick->privmsg(irc->getUser(), "Default value is: " + default_value); nick->privmsg(irc->getUser(), "Type answer:"); } void Request::close() { purple_request_close(type, this); } /* STATIC */ PurpleRequestUiOps Request::uiops = { Request::request_input, Request::request_choice, Request::request_action, Request::request_fields, Request::request_file, Request::request_close, NULL,//finch_request_folder, NULL, NULL, NULL, NULL }; PurpleNotifyUiOps Request::notify_ops = { Request::notify_message, NULL,//finch_notify_email, NULL,//finch_notify_emails, Request::notify_formatted, NULL,//finch_notify_searchresults, NULL,//finch_notify_sr_new_rows, Request::notify_userinfo, NULL,//finch_notify_uri, NULL,//finch_close_notify, /* The rest of the notify-uiops return a GntWidget. // These widgets should be destroyed from here. */ NULL, NULL, NULL, NULL }; vector Request::requests; RequestNick* Request::nick = NULL; void Request::init() { purple_request_set_ui_ops(&uiops); purple_notify_set_ui_ops(¬ify_ops); irc::IRC* irc = Purple::getIM()->getIRC(); nick = new RequestNick(irc, "request", "request", irc->getServerName(), "Eric Cartman"); irc->addNick(nick); } void Request::uninit() { purple_request_set_ui_ops(NULL); purple_notify_set_ui_ops(NULL); } Request* Request::getFirstRequest() { return requests.empty() ? NULL : requests.front(); } Request* Request::addRequest(Request* request) { requests.push_back(request); if(requests.size() == 1) request->display(); else { irc::IRC* irc = Purple::getIM()->getIRC(); nick->privmsg(irc->getUser(), "Hint: Received a new request (" + request->title + "). " "There are " + t2s(requests.size() - 1) + " requests in stack"); } return request; } void* Request::notify_message(PurpleNotifyMsgType type, const char *title, const char *primary, const char *secondary) { irc::IRC* irc = Purple::getIM()->getIRC(); bool titled = false; const char** texts[] = {&title, &primary, &secondary, NULL}; for(const char*** ptr = texts; *ptr != NULL; ++ptr) { if(!**ptr || !***ptr) continue; string txt = **ptr, line; if(!titled) { titled = true; if(txt.find("\n") == string::npos) { nick->privmsg(irc->getUser(), ":: " + txt + " ::"); continue; } } while((line = stringtok(txt, "\r\n")).empty() == false) nick->privmsg(irc->getUser(), line); } return NULL; } void* Request::notify_formatted(const char *title, const char *primary, const char *secondary, const char *text) { irc::IRC* irc = Purple::getIM()->getIRC(); char* text_stripped = purple_markup_strip_html(text); bool titled = false; const char** texts[] = {&title, &primary, &secondary, (const char**) &text_stripped, NULL}; for(const char*** ptr = texts; *ptr != NULL; ++ptr) { if(!**ptr || !***ptr) continue; string txt = **ptr, line; if(!titled) { titled = true; if(txt.find("\n") == string::npos) { nick->privmsg(irc->getUser(), ":: " + txt + " ::"); continue; } } while((line = stringtok(txt, "\r\n")).empty() == false) nick->privmsg(irc->getUser(), line); } g_free(text_stripped); return NULL; } void* Request::notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info) { GList* l = purple_notify_user_info_get_entries(user_info); irc::IRC* irc = Purple::getIM()->getIRC(); irc::Nick* user = irc->getUser(); PurpleBuddy* buddy = purple_find_buddy(gc->account, who); Buddy b(buddy); if(!b.isValid()) { b_log[W_ERR] << "Received WHOIS replies about unknown buddy " << who; return NULL; } for (; l != NULL; l = l->next) { PurpleNotifyUserInfoEntry *user_info_entry = (PurpleNotifyUserInfoEntry*)l->data; //PurpleNotifyUserInfoEntryType type = purple_notify_user_info_entry_get_type(user_info_entry); const char *label = purple_notify_user_info_entry_get_label(user_info_entry); const char *_value = purple_notify_user_info_entry_get_value(user_info_entry); if(!label) continue; string text; if(_value) { char* value = purple_markup_strip_html(_value); text = string(label) + ": " + value; g_free(value); } else text = string(":: ") + label + " ::"; string line; while((line = stringtok(text, "\r\n")).empty() == false) user->send(irc::Message(RPL_WHOISACTUALLY).setSender(irc) .setReceiver(user) .addArg(b.getAlias()) .addArg(line)); } user->send(irc::Message(RPL_ENDOFWHOIS).setSender(irc) .setReceiver(user) .addArg(b.getAlias()) .addArg("End of /WHOIS list")); return NULL; } void Request::closeRequest(const Request* request) { irc::IRC* irc = Purple::getIM()->getIRC(); for(vector::iterator it = requests.begin(); it != requests.end(); ++it) { if(*it != request) continue; bool first = (it == requests.begin()); requests.erase(it); delete request; if(first) { nick->privmsg(irc->getUser(), "Request closed."); if(!requests.empty()) requests.front()->display(); } return; } } void Request::request_close(PurpleRequestType type, void *ui_handle) { closeRequest((Request*)ui_handle); } void* Request::request_input(const char *title, const char *primary, const char *secondary, const char *default_value, gboolean multiline, gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data) { RequestInput* request = new RequestInput(PURPLE_REQUEST_INPUT, Account(account), title ? title : "", primary ? primary : "", secondary ? secondary : "", default_value ? default_value : "", (PurpleRequestInputCb)ok_cb, user_data); return addRequest(request); } void* Request::request_action(const char *title, const char *primary, const char *secondary, int default_value, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t actioncount, va_list actions) { RequestFieldList* request = new RequestFieldList(PURPLE_REQUEST_ACTION, Account(account), title ? title : "", primary ? primary : "", secondary ? secondary : ""); for(size_t i = 0; i < actioncount; ++i) { const char *text = va_arg(actions, const char *); string tmp = text; PurpleRequestActionCb callback = va_arg(actions, PurpleRequestActionCb); request->addField(new RequestFieldAction((int)i, strlower(stringtok(tmp, "_ ")), text, callback, user_data)); } return addRequest(request); } void* Request::request_choice(const char *title, const char *primary, const char *secondary, int default_value, const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, va_list choices) { RequestFieldList* request = new RequestFieldList(PURPLE_REQUEST_CHOICE, Account(account), title ? title : "", primary ? primary : "", secondary ? secondary : ""); const char* text; while ((text = va_arg(choices, const char *))) { int val = va_arg(choices, int); string tmp = text; request->addField(new RequestFieldAction(val, strlower(stringtok(tmp, "_ ")), text, (PurpleRequestChoiceCb)ok_cb, user_data)); } request->addField(new RequestFieldAction(0, "cancel", "Cancel", (PurpleRequestChoiceCb)cancel_cb, user_data)); return addRequest(request); } class RequestFieldsString : public RequestInput { PurpleRequestField* field; public: RequestFieldsString(PurpleRequestField* _field, const Account& account, const string& title, const string& primary, const string& secondary, const string& _default_value) : RequestInput(PURPLE_REQUEST_FIELDS, account, title, primary, secondary, _default_value, NULL, NULL), field(_field) {} virtual void process(const string& answer) const { purple_request_field_string_set_value(field, answer.c_str()); } virtual void close() { Request::closeRequest(this); } }; class RequestFieldsInteger : public RequestInput { PurpleRequestField* field; public: RequestFieldsInteger(PurpleRequestField* _field, const Account& account, const string& title, const string& primary, const string& secondary, const string& _default_value) : RequestInput(PURPLE_REQUEST_FIELDS, account, title, primary, secondary, _default_value, NULL, NULL), field(_field) {} virtual void process(const string& answer) const { purple_request_field_int_set_value(field, s2t(answer)); } virtual void close() { Request::closeRequest(this); } }; class RequestFieldsImage : public Request { PurpleRequestField* field; public: RequestFieldsImage(const Account& account, PurpleRequestField* _field) : Request(PURPLE_REQUEST_FIELDS, account, "","",""), field(_field) {} void process(const string& answer) const { } void close() { Request::closeRequest(this); } void display() const { GError* temp_error = NULL; gchar* temp_filename = NULL; int temp_fd = g_file_open_tmp("minbif_request_XXXXXX.img", &temp_filename, &temp_error); if(temp_error) { b_log[W_ERR] << "Unable to create a temporary file: " << temp_error->message; g_error_free(temp_error); } else { (void)write(temp_fd, purple_request_field_image_get_buffer(field), purple_request_field_image_get_size(field)); ::close(temp_fd); irc::IRC* irc = Purple::getIM()->getIRC(); nick->privmsg(irc->getUser(), string("Image (") + temp_filename + ")"); #ifdef HAVE_CACA CacaImage img(temp_filename); string line, buf = img.getIRCBuffer(60); while((line = stringtok(buf, "\r\n")).empty() == false) { nick->privmsg(irc->getUser(), line); } unlink(temp_filename); #endif /* HAVE_CACA */ } Request* request = Request::getFirstRequest(); if (request) request->close(); } }; class RequestFieldsList : public RequestFieldList { public: RequestFieldsList(const Account& account, const string& title, const string& primary, const string& secondary) : RequestFieldList(PURPLE_REQUEST_FIELDS, account, title, primary, secondary) {} virtual void close() { Request::closeRequest(this); } }; class RequestFieldFieldsBoolean : public RequestField { PurpleRequestField* field; bool value; string text; public: RequestFieldFieldsBoolean(PurpleRequestField* _field, bool _value, string _text) : field(_field), value(_value), text(_text) {} virtual ~RequestFieldFieldsBoolean() {} virtual void setLabel(const string& label) { text = label; } virtual string getLabel() const { return text; } virtual string getText() const { return text; } virtual void runCallback() { purple_request_field_bool_set_value(field, value); } }; class RequestFieldFieldsChoice : public RequestField { PurpleRequestField* field; int value; string text; public: RequestFieldFieldsChoice(PurpleRequestField* _field, int _value, string _text) : field(_field), value(_value), text(_text) {} virtual ~RequestFieldFieldsChoice() {} virtual void setLabel(const string& label) { text = label; } virtual string getLabel() const { return text; } virtual string getText() const { return text; } virtual void runCallback() { purple_request_field_choice_set_value(field, value); } }; class RequestFieldFieldsList : public RequestField { PurpleRequestField* field; int value; string text; public: RequestFieldFieldsList(PurpleRequestField* _field, int _value, string _text) : field(_field), value(_value), text(_text) {} virtual ~RequestFieldFieldsList() {} virtual void setLabel(const string& label) { text = label; } virtual string getLabel() const { return text; } virtual string getText() const { return text; } virtual void runCallback() { purple_request_field_list_clear_selected(field); purple_request_field_list_add_selected(field, text.c_str()); } }; void* Request::request_fields(const char *title, const char *primary, const char *secondary, PurpleRequestFields *fields, const char *ok, GCallback ok_cb, const char *cancel, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *userdata) { GList *gl, *fl; RequestFieldList* mainrequest = new RequestFieldList(PURPLE_REQUEST_FIELDS, Account(account), title ? title : primary ? primary : "", title ? (primary ? primary : (secondary ? secondary : "")) : secondary ? secondary : "", "Do you confirm?"); for(gl = purple_request_fields_get_groups(fields); gl != NULL; gl = gl->next) { PurpleRequestFieldGroup *group = (PurpleRequestFieldGroup*)gl->data; GList* field_list = purple_request_field_group_get_fields(group); for (fl = field_list; fl != NULL; fl = fl->next) { PurpleRequestField *field = (PurpleRequestField *)fl->data; PurpleRequestFieldType type = purple_request_field_get_type(field); if(!purple_request_field_is_visible(field)) continue; const char *field_label = purple_request_field_get_label(field); if(type == PURPLE_REQUEST_FIELD_STRING) { const char* defval = purple_request_field_string_get_default_value(field); RequestFieldsString* request = new RequestFieldsString(field, Account(account), title ? title : primary ? primary : "", secondary ? secondary : "", field_label ? field_label : "", defval ? defval : ""); mainrequest->addSubrequest(request); } else if(type == PURPLE_REQUEST_FIELD_INTEGER) { string defval = t2s(purple_request_field_int_get_default_value(field)); RequestFieldsInteger* request = new RequestFieldsInteger(field, Account(account), title ? title : primary ? primary : "", secondary ? secondary : "", field_label ? field_label : "", defval); mainrequest->addSubrequest(request); } else if(type == PURPLE_REQUEST_FIELD_IMAGE) { RequestFieldsImage* request = new RequestFieldsImage(Account(account), field); mainrequest->addSubrequest(request); } else { RequestFieldsList* request = new RequestFieldsList(Account(account), title ? title : primary ? primary : "", secondary ? secondary : "", field_label ? field_label : ""); switch(type) { case PURPLE_REQUEST_FIELD_BOOLEAN: request->addField(new RequestFieldFieldsBoolean(field, true, "true")); request->addField(new RequestFieldFieldsBoolean(field, false, "false")); break; case PURPLE_REQUEST_FIELD_CHOICE: { GList *labels = purple_request_field_choice_get_labels(field); GList *l; int i = 0; for (l = labels; l != NULL; l = l->next, ++i) request->addField(new RequestFieldFieldsChoice(field, i, (const char*)l->data)); break; } case PURPLE_REQUEST_FIELD_LIST: { GList *l; int i = 0; for (l = purple_request_field_list_get_items(field); l != NULL; l = l->next, ++i) request->addField(new RequestFieldFieldsList(field, i, (const char*)l->data)); break; } default: b_log[W_ERR] << "Skipped a field " << type; delete request; continue; } mainrequest->addSubrequest(request); } } } mainrequest->addField(new RequestFieldTmplt(0, "ok", ok, (PurpleRequestFieldsCb)ok_cb, userdata, fields)); mainrequest->addField(new RequestFieldTmplt(1, "cancel", cancel, (PurpleRequestFieldsCb)cancel_cb, userdata, fields)); return addRequest(mainrequest); } void* Request::request_file(const char *title, const char *filename, gboolean savedialog, GCallback ok_cb, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data) { if(conf.GetSection("file_transfers")->GetItem("enabled")->Boolean() == false) { b_log[W_ERR] << "File transfers are disabled on this server."; ((PurpleRequestFileCb)cancel_cb)(user_data, NULL); return NULL; } if(savedialog) { /* This is probably (I hope) a request to know the path where * to save a file that someone sends to me. * * Here we try to put it in /var/lib/minbif/romain/downloads/, * and create directory if it doesn't exist. * * Important: the file returned MUST be writtable! If libpurple * tries to overload a unwrittable file, it'll write * an error, but also recall this function, and it * can produce an unfinite loop. */ string path = purple_user_dir(); path += "/downloads/"; // XXX do not hardcode this. if(!check_write_file(path, filename)) { b_log[W_ERR] << "Unable to write into the download directory '" << path << "': " << strerror(errno); ((PurpleRequestFileCb)cancel_cb)(user_data, NULL); return NULL; } path += filename; ((PurpleRequestFileCb)ok_cb)(user_data, path.c_str()); } else { /* TODO Implement when libpurple requests to open a file. */ nick->request_error("Warning: something tries to ask you to open a filename (" + string(title ? title : "") + "). But it is not yet implemented."); ((PurpleRequestFileCb)cancel_cb)(user_data, NULL); } return NULL; } }; /* namespace im */ minbif-1.0.5+git20120508/src/im/im.cpp0000644000175000017500000001552211754151773015013 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include "im.h" #include "purple.h" #include "irc/irc.h" #include "irc/user.h" #include "core/log.h" #include "core/util.h" namespace im { /* STATIC */ string IM::path; void IM::setPath(const string& _path) { DIR* d; path = _path; if(!(d = opendir(path.c_str()))) { if(mkdir(path.c_str(), 0700) < 0) throw IMError("Unable to create directory '" + path + "': " + strerror(errno)); } else closedir(d); } bool IM::exists(const string& username) { DIR* d; if(!(d = opendir((path + "/" + username).c_str()))) return false; closedir(d); return true; } /* METHODS */ IM::IM(irc::IRC* _irc, string _username) : username(_username), user_path(path + "/" + username), irc(_irc) { DIR* d; if(!(d = opendir(user_path.c_str()))) { if(mkdir(user_path.c_str(), 0700) < 0) throw IMError("Unable to create user directory '" + user_path + "': " + strerror(errno)); } else closedir(d); try { Purple::init(this); } catch(PurpleError &e) { throw IMError(e.Reason()); } } IM::~IM() { purple_core_quit(); } void IM::restore() { if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status")) purple_savedstatus_activate(purple_savedstatus_get_startup()); purple_accounts_restore_current_statuses(); } void IM::setPassword(const string& password) { purple_prefs_set_string("/minbif/password", password.c_str()); } string IM::getPassword() const { return purple_prefs_get_string("/minbif/password"); } void IM::setTypingNotice(bool enabled) { purple_prefs_set_int("/minbif/typing_notice", enabled ? 1 : 0); } bool IM::hasTypingNotice() const { return purple_prefs_get_int("/minbif/typing_notice"); } void IM::setAcceptNoBuddiesMessages(bool enabled) { purple_prefs_set_bool("/minbif/accept_nobuddies_messages", enabled); } bool IM::hasAcceptNoBuddiesMessages() const { return purple_prefs_get_bool("/minbif/accept_nobuddies_messages"); } void IM::setSendDelay(int delay) { purple_prefs_set_int("/minbif/send_delay", delay); } int IM::getSendDelay() const { return purple_prefs_get_int("/minbif/send_delay"); } void IM::setVoicedBuddies(bool enabled) { if(hasVoicedBuddies() != enabled) { map accs = getAccountsList(); purple_prefs_set_bool("/minbif/voiced_buddies", enabled); for(map::iterator it = accs.begin(); it != accs.end(); ++it) it->second.updatedAllBuddies(); } } bool IM::hasVoicedBuddies() const { return purple_prefs_get_bool("/minbif/voiced_buddies"); } void IM::setServerAliases(bool enabled) { purple_prefs_set_bool("/minbif/server_aliases", enabled); } bool IM::hasServerAliases() const { return purple_prefs_get_bool("/minbif/server_aliases"); } void IM::setAwayIdle(bool enabled) { purple_prefs_set_bool("/purple/away/away_when_idle", enabled); } bool IM::hasAwayIdle() const { return purple_prefs_get_bool("/purple/away/away_when_idle"); } map IM::getProtocolsList() const { return Purple::getProtocolsList(); } map IM::getPluginsList() const { return Purple::getPluginsList(); } Protocol IM::getProtocol(string id) const { map plist = getProtocolsList(); map::iterator it = plist.find(id); if(it == plist.end()) throw ProtocolUnknown(); else return it->second; } map IM::getAccountsList() const { return Purple::getAccountsList(); } Account IM::getAccount(string name) const { map alist = getAccountsList(); for(map::iterator it = alist.begin(); it != alist.end(); ++it) if(it->second.getServername() == name || it->second.getID() == name) return it->second; return Account(); } Account IM::getAccountFromChannel(string name) const { map alist = getAccountsList(); for(map::iterator it = alist.begin(); it != alist.end(); ++it) if(it->second.getStatusChannelName() == name) return it->second; return Account(); } Account IM::addAccount(const Protocol& proto, const string& username, const Protocol::Options& options, bool register_account) { return Purple::addAccount(proto, username, options, register_account); } void IM::delAccount(Account user) { Purple::delAccount(user.getPurpleAccount()); } void IM::setBuddyIcon(const string& path) { map alist = getAccountsList(); for(map::iterator it = alist.begin(); it != alist.end(); ++it) it->second.setBuddyIcon(path); } string IM::getBuddyIconPath() const { return string(purple_user_dir()) + "/buddy_icon/"; } bool IM::isAway() const { PurpleStatusPrimitive prim = PURPLE_STATUS_AVAILABLE; map alist = getAccountsList(); for(map::iterator it = alist.begin(); it != alist.end(); ++it) if (it->second.getStatus() > PURPLE_STATUS_AVAILABLE) prim = it->second.getStatus(); for(map::iterator it = alist.begin(); it != alist.end(); ++it) if (it->second.getStatus() != PURPLE_STATUS_OFFLINE && it->second.getStatus() <= PURPLE_STATUS_AVAILABLE) it->second.setStatus(prim); return prim != PURPLE_STATUS_AVAILABLE; } bool IM::setStatus(string away) { PurpleStatusPrimitive prim = PURPLE_STATUS_AVAILABLE; if(away.empty() == false) { string s = strlower(away); int i; /* Looking for an existant status equivalent to the away message. */ for(i = 0; ( i < PURPLE_STATUS_NUM_PRIMITIVES && strlower(purple_primitive_get_name_from_type((PurpleStatusPrimitive)i)) != s && purple_primitive_get_id_from_type((PurpleStatusPrimitive)i) != s ); ++i) ; if(i >= PURPLE_STATUS_NUM_PRIMITIVES) { /* If the status does not exist, set the AWAY status and set the message * given in the \b away string as the status message. */ prim = PURPLE_STATUS_AWAY; } else { prim = (PurpleStatusPrimitive)i; away.clear(); /* do not change the saved status message. */ } } map alist = getAccountsList(); for(map::iterator it = alist.begin(); it != alist.end(); ++it) it->second.setStatus(prim, away); return true; } }; /* namespace im */ minbif-1.0.5+git20120508/src/core/0000755000175000017500000000000011754151773014220 5ustar sebsebminbif-1.0.5+git20120508/src/core/exception.h0000644000175000017500000000245411754151773016374 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _MINBIF_EXCEPTION_H #define _MINBIF_EXCEPTION_H #include #include class StrException : public std::exception { std::string reason; public: StrException(const std::string& _reason) : reason(_reason) {} virtual ~StrException() throw() {} const std::string& Reason() const { return reason; } }; #define EXCEPTION(x) class x : public std::exception {}; #define STREXCEPTION(x) class x : public StrException { public: x(const std::string& reason) : StrException(reason) {} }; #endif /* _MINBIF_EXCEPTION_H */ minbif-1.0.5+git20120508/src/core/log.cpp0000644000175000017500000000604411754151773015511 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "util.h" #include "log.h" #include "server_poll/poll.h" Log b_log; static struct { uint32_t flag; int level; const char* s; bool sys_log; bool propagate_to_user; } all_flags[] = { { W_SNO, LOG_NOTICE, "" , false, true }, { W_DEBUG, LOG_DEBUG, "DEBUG" , false, true }, { W_PARSE, LOG_DEBUG, "PARSE" , false, true }, { W_PURPLE, LOG_DEBUG, "PURPLE" , false, true }, { W_DESYNCH, LOG_WARNING, "DESYNCH", false, true }, { W_WARNING, LOG_WARNING, "WARNING", false, true }, { W_ERR, LOG_ERR, "ERR" , true, true }, { W_INFO, LOG_NOTICE, "INFO" , false, true }, { W_SOCK, LOG_DEBUG, "SOCK" , false, false }, }; Log::flux::~flux() { int i; if(!(flag & b_log.getLoggedFlags())) return; for(i = (sizeof all_flags / sizeof *all_flags) - 1; i >= 0 && !(flag & all_flags[i].flag); --i) ; if(i < 0) syslog(LOG_WARNING, "[SYSLOG] (%X) Unable to find how to log this message: %s", (uint32_t)flag, str.c_str()); else { if(all_flags[i].sys_log && b_log.toSyslog()) syslog(all_flags[i].level, "[%s] %s", all_flags[i].s, str.c_str()); if (!all_flags[i].propagate_to_user) return; struct timeval t; gettimeofday(&t, NULL); string category; if(flag & W_SNO) category = "*** Notice -- "; else category = string("[") + all_flags[i].s + "] "; if(b_log.getServerPoll()) b_log.getServerPoll()->log(flag, category + str); } } Log::Log() : logged_flags(DEFAULT_LOGGED_FLAGS), poll(NULL) { openlog("minbif", LOG_CONS, LOG_DAEMON); } Log::~Log() { closelog(); } std::string Log::formatLoggedFlags() const { std::string s; int i; for(i = (sizeof all_flags / sizeof *all_flags) - 1; i >= 0; --i) if(logged_flags & all_flags[i].flag) { if(!s.empty()) s.append(" "); s += all_flags[i].s; } return s; } void Log::setLoggedFlags(std::string s, bool _to_syslog) { std::string token; to_syslog = _to_syslog; logged_flags = 0; while((token = stringtok(s, " ")).empty() == false) { int i; if(token == "ALL") { logged_flags = (uint32_t) -1; break; } for(i = (sizeof all_flags / sizeof *all_flags) - 1; i >= 0 && (token != all_flags[i].s); --i) ; if(i >= 0) logged_flags |= all_flags[i].flag; } } minbif-1.0.5+git20120508/src/core/util.h0000644000175000017500000000330411754151773015346 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef UTIL_H #define UTIL_H #include #include #include using std::string; string stringtok(string &in, const char * const delimiters); template T s2t(const std::string & Str) { T Dest; std::istringstream iss( Str ); iss >> Dest; return Dest; } template std::string t2s( const T & Value ) { std::ostringstream oss; oss << Value; return oss.str(); } /** Return a valid std::string from a pointer. */ static inline string p2s(const char* s) { return s ? s : ""; } guint glib_input_add(gint fd, PurpleInputCondition condition, PurpleInputFunction function, gpointer data); string strupper(string s); string strlower(string s); gchar* markup2irc(const gchar* markup); gchar* irc2markup(const gchar* string); bool is_ip(const char *ip); bool check_write_file(string path, string filename); #define FOREACH(t, v, it) \ for(t::iterator it = v.begin(); it != v.end(); ++it) #endif /* UTIL_H */ minbif-1.0.5+git20120508/src/core/mutex.cpp0000644000175000017500000000535411754151773016075 0ustar sebseb/* * Copyright(C) 2008 Laurent Defert, Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * This product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim * Hudson (tjh@cryptsoft.com). * * */ #include #include #include #include #include #include #include #include "mutex.h" void Mutex::Init(MutexType _type) { mutex = (pthread_mutex_t*) new pthread_mutex_t; type = _type; pthread_mutexattr_t attr; if(pthread_mutexattr_init(&attr) != 0) { std::cerr << "pthread_attr_init: " << strerror(errno); throw MutexError(); } switch(type) { case NORMAL_MUTEX: //#ifdef DEBUG if(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK) != 0) { std::cerr << "pthread_mutexattr_settype: " << strerror(errno) << std::endl; throw MutexError(); } //#else // pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); //#endif break; case RECURSIVE_MUTEX: if(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) { std::cerr << "pthread_mutexattr_settype: " << strerror(errno) << std::endl; throw MutexError(); } break; default: assert(false); } if(pthread_mutex_init(mutex, &attr) != 0) { std::cerr << "pthread_mutex_init: " << strerror(errno) << std::endl; throw MutexError(); } pthread_mutexattr_destroy(&attr); } Mutex::Mutex(MutexType type) { Init(type); } Mutex::Mutex(const Mutex& m) { Init(m.type); } Mutex& Mutex::operator=(const Mutex& m) { std::cerr << "mutex so bad" << std::endl; Init(m.type); return *this; } Mutex::~Mutex() { pthread_mutex_destroy(mutex); delete mutex; } void Mutex::Lock() const { int res = pthread_mutex_lock(mutex); //assert(res == 0); /* Don't use pf_log, because it locks the stdout mutex. */ if(res) std::cerr << "Failed to lock " << this << std::endl; } void Mutex::Unlock() const { int res = pthread_mutex_unlock(mutex); //assert(res == 0); /* Don't use pf_log, because it locks the stdout mutex. */ if(res) std::cerr << "Failed to unlock " << this << std::endl; } minbif-1.0.5+git20120508/src/core/entity.h0000644000175000017500000000203111754151773015701 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef ENTITY_H #define ENTITY_H #include using std::string; class Entity { string name; public: Entity(string _name) : name(_name) {} virtual ~Entity() {} string getName() const { return name; } void setName(string n) { name = n; } virtual string getLongName() const { return name; } }; #endif /* ENTITY_H */ minbif-1.0.5+git20120508/src/core/caca_image.cpp0000644000175000017500000001226111754151773016757 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #ifdef HAVE_CACA #include #include #endif #include "caca_image.h" #include #ifdef HAVE_CACA struct CacaImage::image { char *pixels; unsigned int w, h; cucul_dither_t *dither; void *priv; unsigned ref; image(); image(const image& img); ~image(); void create_dither(unsigned bpp); static struct CacaImage::image * load_file(char const * name); }; #endif /* HAVE_CACA */ CacaImage::CacaImage() : width(0), height(0), font_width(16), font_height(10), img(0) {} CacaImage::CacaImage(string path) : width(0), height(0), font_width(6), font_height(10), img(NULL) { #ifdef HAVE_CACA img = image::load_file(path.c_str()); #endif } CacaImage::CacaImage(const void* buf, size_t size, unsigned buf_width, unsigned buf_height, unsigned bpp) : width(0), height(0), font_width(6), font_height(10), img(NULL) { #ifdef HAVE_CACA img = new image(); img->w = buf_width; img->h = buf_height; img->pixels = g_strndup((const char*)buf, size); img->create_dither(bpp); #endif } CacaImage::CacaImage(const CacaImage& caca) : buf(caca.buf), width(caca.width), height(caca.height), font_width(caca.font_width), font_height(caca.font_height), img(caca.img) { #ifdef HAVE_CACA img->ref++; #endif } CacaImage& CacaImage::operator=(const CacaImage& caca) { deinit(); buf = caca.buf; width = caca.width; height = caca.height; font_width = caca.font_width; font_height = caca.font_height; img = caca.img; #ifdef HAVE_CACA img->ref++; #endif return *this; } CacaImage::~CacaImage() { deinit(); } void CacaImage::deinit() { #ifdef HAVE_CACA if(img) { img->ref--; if(img->ref < 1) delete img; img = NULL; } #endif } string CacaImage::getIRCBuffer() { if(buf.empty()) return getIRCBuffer(width, height, "irc", font_width, font_height); return buf; } string CacaImage::getIRCBuffer(unsigned _width, unsigned _height, const char *output_type, unsigned _font_width, unsigned _font_height) { #ifndef HAVE_CACA throw CacaNotLoaded(); #else if(!img) throw CacaError(); if(buf.empty() == false && width == _width && height == _height && font_width == _font_width && font_height == _font_height) return buf; width = _width; height = _height; font_width = _font_width; font_height = _font_height; cucul_canvas_t *cv = cucul_create_canvas(0, 0); if(!cv) throw CacaError(); if(!width && !height) { height = 10; width = height * img->w * font_height / img->h / font_width; } else if(width && !height) height = width * img->h * font_width / img->w / font_height; else if(!width && height) width = height * img->w * font_height / img->h / font_width; cucul_set_canvas_size(cv, width, height); cucul_set_color_ansi(cv, CUCUL_DEFAULT, CUCUL_TRANSPARENT); cucul_clear_canvas(cv); if(cucul_set_dither_algorithm(img->dither, "fstein")) { cucul_free_canvas(cv); throw CacaError(); } cucul_dither_bitmap(cv, 0, 0, width, height, img->dither, img->pixels); size_t len; char* tmp; #ifdef HAVE_OLD_CACA tmp = (char*)cucul_export_memory(cv, output_type, &len); #else tmp = (char*)caca_export_canvas_to_memory(cv, output_type, &len); #endif if(!tmp) throw CacaError(); tmp = (char*)realloc(tmp, len+1); tmp[len] = 0; buf = tmp; free(tmp); cucul_free_canvas(cv); return buf; #endif /* HAVE_CACA */ } #ifdef HAVE_CACA CacaImage::image::image() : pixels(0), w(0), h(0), dither(0), priv(0), ref(1) { } CacaImage::image::~image() { if(priv) imlib_free_image(); if(dither) cucul_free_dither(dither); free(pixels); } void CacaImage::image::create_dither(unsigned int bpp) { unsigned int depth, rmask, gmask, bmask, amask; rmask = 0x00ff0000; gmask = 0x0000ff00; bmask = 0x000000ff; amask = 0xff000000; depth = bpp / 8; /* Create the libcaca dither */ dither = cucul_create_dither(bpp, w, h, depth * w, rmask, gmask, bmask, amask); } struct CacaImage::image* CacaImage::image::load_file(char const * name) { struct image * im = new image(); Imlib_Image image; /* Load the new image */ image = imlib_load_image(name); if(!image) { delete im; return NULL; } imlib_context_set_image(image); im->w = imlib_image_get_width(); im->h = imlib_image_get_height(); im->pixels = (char*)malloc(im->w * im->h * 4); memcpy(im->pixels, imlib_image_get_data_for_reading_only(), im->w * im->h * 4); im->create_dither(32); if(!im->dither) { delete im; return NULL; } im->priv = (void *)image; return im; } #endif /* HAVE_CACA */ minbif-1.0.5+git20120508/src/core/config.cpp0000644000175000017500000002623211754151773016176 0ustar sebseb/* config.cpp - This is a library to make a configuration * * Copyright (C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include "config.h" MyConfig conf; /* Définition de quelques macros utiles */ #undef FOR #define FOR(T, v, x) \ T (x); \ for(T::iterator x##it = (v).begin(); x##it != (v).end() && (x = x##it->second); ++x##it) #undef FORit #define FORit(T, v, x) \ for(T::iterator (x) = (v).begin(); (x) != (v).end(); ++(x)) #define FORmm(T, v, label, x) \ T::iterator x##lb = v.lower_bound(label); \ T::iterator x##ub = v.upper_bound(label); \ for(T::iterator x = x##lb; x != x##ub; ++x) #define Error(x) do { std::cerr << path << ":" << line_count << ": " << x << std::endl ; error = true; } while(0) std::string stringtok(std::string &in, const char * const delimiters = " \t\n"); /******************************************************************************************** * Config * ********************************************************************************************/ MyConfig::MyConfig(std::string _path) : path(_path), loaded(false) { } MyConfig::MyConfig() : loaded(false) { } MyConfig::~MyConfig() { FORit(SectionMap, sections, it) delete it->second; } bool MyConfig::Load(std::string _path) { if(!_path.empty()) this->path = _path; if(path.empty()) throw error_exc("Filename is empty"); std::ifstream fp(path.c_str()); if(!fp) { std::cerr << path + ": File not found" << std::endl; return false; } Clean(); std::string ligne; line_count = 0; bool error = false; ConfigSection* section = 0; while(std::getline(fp, ligne)) { ++line_count; const char* ptr = ligne.c_str(); while(ptr && *ptr && (*ptr == ' ' || *ptr == '\t')) ++ptr; ligne = ptr; if(ligne.empty() || ligne[0] == '#' || ligne[0] == '\r' || ligne[0] == '\n') continue; if(ligne.find('=') != std::string::npos) { if(!section) { Error("We aren't in a section !"); continue; } std::string label = stringtok(ligne, " "); ConfigItem* item = section->GetItem(label); if(!item) { Error("Unknown item '" << label << "'"); continue; } if(ligne.empty()) { Error("There isn't any value"); continue; } std::string value; ptr = ligne.c_str(); while(ptr && *ptr && (*ptr == ' ' || *ptr == '=' || *ptr == '\t')) ++ptr; if(!ptr || !*ptr) { Error("There isn't any value"); continue; } value = ptr; if(section->NameItem() == item) { ConfigSection* s = 0; if(section->Parent()) s = section->Parent()->GetSection(section->Label(), value); else s = GetSection(section->Label(), value); if(s) { Error("There is already a section named '" << value << "' ('" << s->Label() << "')"); continue; } section->SetName(value); } item->SetFound(); if(!item->SetValue(value)) Error("'" << value << "' is an incorrect value for '" << item->Label() << "' item (type of value is " << item->ValueType() << ")"); else if(item->CallBack()) item->CallBack() (item); } else if(!ligne.empty() && ligne[0] == '}') { if(!section) { Error("a '}' out of a section !?"); continue; } if(section->IsCopy() && section->Name().empty()) Error("Section '" << section->Label() << "' hasn't a name"); section = section->Parent(); } else { std::string tab = stringtok(ligne, " "); if(!section) section = GetSection(tab); else section = section->GetSection(tab); if(!section) { Error("Unknown section '" << tab << "'"); continue; } section->SetFound(); if(section->IsMultiple()) { section = new ConfigSection(*section); section->SetCopy(); if(section->Parent()) section->Parent()->AddSection(section); else AddSection(section); } } } if(section) Error("in «" + section->Label() + (section->Name().empty() ? "" : ("(" + section->Name() + ")")) + "»: '}' not found to close section !"); if(FindEmpty()) // Find empty sections error = true; if(!error) loaded = true; return !error; } ConfigSection* MyConfig::GetSection(std::string label) { FORmm(SectionMap, sections, label, it) if(it->second->IsCopy() == false) return it->second; return 0; } ConfigSection* MyConfig::GetSection(std::string label, std::string name) { FORmm(SectionMap, sections, label, it) if(it->second->Label() == label && it->second->IsMultiple() && it->second->Name() == name) return it->second; return 0; } std::vector MyConfig::GetSectionClones(std::string label) { std::vector s; FORmm(SectionMap, sections, label, it) if(it->second->Label() == label && it->second->IsMultiple() && it->second->IsCopy()) s.push_back(it->second); return s; } ConfigSection* MyConfig::AddSection(ConfigSection* section) { sections.insert(std::make_pair(section->Label(), section)); return section; } ConfigSection* MyConfig::AddSection(std::string label, std::string description, MyConfig::section_t type) throw(MyConfig::error_exc) { if(loaded) throw error_exc("Configuration is already loaded !"); FORit(SectionMap, sections, it) if(it->second->Label() == label) throw error_exc("Section " + label + " has a name already used"); return AddSection(new ConfigSection(label, description, type, this, 0)); } void MyConfig::Clean() { for(SectionMap::iterator it = sections.begin(); it != sections.end();) { if(it->second->IsCopy()) { delete it->second; sections.erase(it++); } else { it->second->SetFound(false); it->second->Clean(); ++it; } } } bool MyConfig::FindEmpty() { std::string begin = "in global porty: "; bool error = false; // Error() macro change this value FORit(SectionMap, sections, it) if(!it->second->Found() && !it->second->IsOptional()) { ConfigSection* s = it->second; Error(begin << "missing section '" << s->Label() << "' (" << s->Description() << ")"); } else it->second->FindEmpty(); return error; } /******************************************************************************************** * ConfigSection * ********************************************************************************************/ ConfigSection::ConfigSection(std::string _label, std::string _description, MyConfig::section_t _type, MyConfig* _config, ConfigSection* _parent) : label(_label), description(_description), type(_type), config(_config), parent(_parent), copy(false), name_item(0), found(false) { items.clear(); } ConfigSection::ConfigSection(ConfigSection& cs) : label(cs.label), description(cs.description), type(cs.type), config(cs.config), parent(cs.parent), copy(cs.copy), name_item(0), found(cs.found) { FORit(SectionMap, cs.sections, it) sections.insert(std::make_pair(it->second->label, new ConfigSection(*it->second))); FORit(ItemMap, cs.items, it) { ConfigItem* item = it->second->Clone(); items[it->second->Label()] = item; if(cs.name_item == it->second) name_item = item; } } ConfigSection::~ConfigSection() { FORit(SectionMap, sections, it) delete it->second; FORit(ItemMap, items, it) delete it->second; } ConfigSection* ConfigSection::GetSection(std::string label) { FORmm(SectionMap, sections, label, it) if(it->second->IsCopy() == false) return it->second; return 0; } ConfigSection* ConfigSection::GetSection(std::string label, std::string name) { FORmm(SectionMap, sections, label, it) if(it->second->Label() == label && it->second->IsMultiple() && it->second->Name() == name) return it->second; return 0; } std::vector ConfigSection::GetSectionClones(std::string label) { std::vector s; FORmm(SectionMap, sections, label, it) if(it->second->Label() == label && it->second->IsMultiple() && it->second->IsCopy()) s.push_back(it->second); return s; } void ConfigSection::Clean() { for(SectionMap::iterator it = sections.begin(); it != sections.end();) { if(it->second->IsCopy()) { delete it->second; sections.erase(it++); } else { it->second->SetFound(false); it->second->Clean(); ++it; } } for(ItemMap::iterator it = items.begin(); it != items.end(); ++it) it->second->SetFound(false); } bool ConfigSection::FindEmpty() { std::string begin = "in «" + Label() + (Name().empty() ? "" : ("(" + Name() + ")")) + "»: "; bool error = false; // Error() macro change this value int line_count = config->NbLines(); /* Récuperation des informations de MyConfig */ std::string path = config->Path(); /* pour pouvoir utiliser la macro Error() */ if (IsMultiple() && !IsCopy()) return true; FORit(ItemMap, items, it) if(!it->second->Found()) { ConfigItem* item = it->second; if((item->DefValue().empty() || item->SetValue(item->DefValue()) == false) && Found()) Error(begin << "missing item '" << item->Label() << "' (" << item->Description() << ")"); } FORit(SectionMap, sections, it) if(!it->second->Found() && it->second->IsOptional() == false) { ConfigSection* s = it->second; Error(begin << "missing section '" << s->Label() << "' (" << s->Description() << ")"); } else it->second->FindEmpty(); return error; } ConfigSection* ConfigSection::AddSection(ConfigSection* section) { sections.insert(std::make_pair(section->Label(), section)); return section; } ConfigSection* ConfigSection::AddSection(std::string label, std::string description, MyConfig::section_t type) throw(MyConfig::error_exc) { FORit(SectionMap, sections, it) if(it->second->Label() == label) throw MyConfig::error_exc("Section " + label + " has a name already used in section \"" + Label() + "\""); return AddSection(new ConfigSection(label, description, type, config, this)); } ConfigItem* ConfigSection::GetItem(std::string label) { ItemMap::iterator it = items.find(label); if(it == items.end()) return 0; else return it->second; } void ConfigSection::AddItem(ConfigItem* item, bool is_name) throw(MyConfig::error_exc) { try { if(!item) throw MyConfig::error_exc("You have to give an item !!"); if(items[item->Label()] != 0) throw MyConfig::error_exc("There is already an item in \"" + Label() + "\" section named \"" + item->Label() + "\""); if(is_name && name_item) throw MyConfig::error_exc("I want to add an 'is_name' item, but I have already one !"); item->config = config; item->parent = this; items[item->Label()] = item; if(is_name) name_item = item; } catch(...) { delete item; throw; } } minbif-1.0.5+git20120508/src/core/log.h0000644000175000017500000000641211754151773015155 0ustar sebseb/* * Copyright(C) 2009-2010 Laurent Defert, Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef LOG_H #define LOG_H #include #include #include #include "core/exception.h" enum { W_DEBUG = 1 << 0, /* Debug */ W_PARSE = 1 << 1, /* Show parsing */ W_PURPLE = 1 << 2, /* Routing information */ W_DESYNCH = 1 << 3, /* Desynchronization */ W_WARNING = 1 << 4, /* Warnings */ W_ERR = 1 << 5, /* Errors */ W_INFO = 1 << 6, /* Info */ W_SNO = 1 << 7, /* Server notice */ W_SOCK = 1 << 8 /* Socket error */ }; #define DEFAULT_LOGGED_FLAGS (W_DESYNCH|W_WARNING|W_ERR|W_INFO|W_SNO) #define FLog(flags, msg) b_log[flags] << __FILE__ << ":" << __PRETTY_FUNCTION << "():" << __LINE__ << ": " << msg /*************************** * Log * *************************** * * Usage: * b_log[flags] << messages; * * Examples: * b_log[W_WARNING] << cl << "There is a problem with this: " << i; * b_log[W_ERR] << "This user isn't allowed to do this!"; */ class ServerPoll; class Log { public: Log(); ~Log(); void setLoggedFlags(std::string s, bool to_syslog = true); std::string formatLoggedFlags() const; uint32_t getLoggedFlags() const { return logged_flags; } bool toSyslog() const { return to_syslog; } void setServerPoll(const ServerPoll* _poll) { poll = _poll; } const ServerPoll* getServerPoll() const { return poll; } class flux { std::string str; size_t flag; public: flux(size_t i) : flag(i) {} ~flux(); template flux& operator<< (T s) { std::ostringstream oss; oss << s; str += oss.str(); return *this; } }; flux operator[](size_t __n) { return flux(__n); } template flux operator<<(T v) { return flux(W_ERR) << v; } private: uint32_t logged_flags; bool to_syslog; const ServerPoll* poll; }; template<> inline Log::flux& Log::flux::operator<< (std::string s) { str += s; return *this; } extern Log b_log; class LogException : public StrException { uint32_t log_level; public: LogException(const std::string& reason, uint32_t _log_level = W_ERR) : StrException(reason), log_level(_log_level) { b_log[log_level] << Reason(); } }; #define LOGEXCEPTION(x) class x : public LogException { public: x(const std::string& reason, uint32_t log_level = W_ERR) : LogException(reason, log_level) {} }; #define LOGEXCEPTION2(x, y, z) class x : public y { public: x(const std::string& reason, uint32_t log_level = z) : y(reason, log_level) {} }; #endif /* LOG_H */ minbif-1.0.5+git20120508/src/core/caca_image.h0000644000175000017500000000413311754151773016423 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CACA_IMAGE_H #define CACA_IMAGE_H #include #include using std::string; /** Raised when libcaca can't decode image */ class CacaError : public std::exception {}; /** Raised when libcaca isn't loaded. */ class CacaNotLoaded : public std::exception {}; /** Convert an image (JPG/PNG/..) to a beautiful ASCII-art picture. */ class CacaImage { struct image; string buf; unsigned width, height, font_width, font_height; image* img; void deinit(); public: /** Empty constructor */ CacaImage(); /** Constructor from file. * * @param path path to file */ CacaImage(string path); /** Constructor from buffer */ CacaImage(const void* buffer, size_t size, unsigned width, unsigned height, unsigned bpp); CacaImage(const CacaImage& caca); CacaImage& operator=(const CacaImage& caca); ~CacaImage(); /** Get IRC buffer to ASCII art picture. * If buffer is empty, it builds it. * * @param width render's text width * @param height render's text height * @param font_width font width * @param font_height font height * @return buffer of picture. */ string getIRCBuffer(unsigned width, unsigned height = 0, const char* output_type = "irc", unsigned font_width = 6, unsigned font_height = 10); /** Get IRC buffer to ASCII art picture. * If buffer is empty, it builds it with default parameters. */ string getIRCBuffer(); }; #endif /* CACA_IMAGE_H */ minbif-1.0.5+git20120508/src/core/version.h0000644000175000017500000000433111754151773016057 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef VERSION_H #define VERSION_H #define MINBIF_VERSION_NAME "minbif" #define MINBIF_WEBSITE "http://minbif.im" #define MINBIF_DEV_WEBSITE "http://symlink.me/projects/show/minbif" #define MINBIF_VERSION_MAJOR "1" #define MINBIF_VERSION_MINOR "0.5" #define MINBIF_VERSION_PATCH "" #define MINBIF_VERSION_EXTRA "" #ifdef HAVE_CACA #define MINBIF_SUPPORTS_CACA "(caca)" #else #define MINBIF_SUPPORTS_CACA "" #endif #ifdef HAVE_VIDEO #define MINBIF_SUPPORTS_VIDEO "(video)" #else #define MINBIF_SUPPORTS_VIDEO "" #endif #ifdef HAVE_PAM #define MINBIF_SUPPORTS_PAM "(pam)" #else #define MINBIF_SUPPORTS_PAM "" #endif #ifdef HAVE_TLS #define MINBIF_SUPPORTS_TLS "(tls)" #else #define MINBIF_SUPPORTS_TLS "" #endif #define MINBIF_SUPPORTS MINBIF_SUPPORTS_CACA \ MINBIF_SUPPORTS_VIDEO \ MINBIF_SUPPORTS_PAM \ MINBIF_SUPPORTS_TLS #define MINBIF_BUILD ("(Build at " __DATE__ " " __TIME__ " with libpurple-" \ + t2s(PURPLE_MAJOR_VERSION) + "." \ + t2s(PURPLE_MINOR_VERSION) + "." \ + t2s(PURPLE_MICRO_VERSION) + ")") #define MINBIF_VERSION MINBIF_VERSION_NAME "-" \ MINBIF_VERSION_MAJOR "." \ MINBIF_VERSION_MINOR \ MINBIF_VERSION_PATCH \ MINBIF_VERSION_EXTRA \ MINBIF_SUPPORTS /** NULL terminated array. */ extern const char* infotxt[]; #endif /* VERSION_H */ minbif-1.0.5+git20120508/src/core/util.cpp0000644000175000017500000002433511754151773015710 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2011 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include "util.h" string stringtok(string &in, const char * const delimiters) { string::size_type i = 0; string s; // eat leading whitespace i = in.find_first_not_of (delimiters, i); // find the end of the token string::size_type j = in.find_first_of (delimiters, i); if (j == string::npos) { if(i == string::npos) s = ""; else s = in.substr(i); in = ""; return s; // nothing left but white space } // push token s = in.substr(i, j-i); in = in.substr(j+1); return s; } typedef struct _PurpleGLibIOClosure { PurpleInputFunction function; guint result; gpointer data; } PurpleGLibIOClosure; #define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR) #define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL) static void purple_glib_io_destroy(gpointer data) { g_free(data); } static gboolean purple_glib_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data) { PurpleGLibIOClosure *closure = (PurpleGLibIOClosure*)data; PurpleInputCondition purple_cond = (PurpleInputCondition)0; if (condition & PURPLE_GLIB_READ_COND) purple_cond = (PurpleInputCondition)(purple_cond|PURPLE_INPUT_READ); if (condition & PURPLE_GLIB_WRITE_COND) purple_cond = (PurpleInputCondition)(purple_cond|PURPLE_INPUT_WRITE); closure->function(closure->data, g_io_channel_unix_get_fd(source), purple_cond); return TRUE; } guint glib_input_add(gint fd, PurpleInputCondition condition, PurpleInputFunction function, gpointer data) { PurpleGLibIOClosure *closure = g_new0(PurpleGLibIOClosure, 1); GIOChannel *channel; GIOCondition cond = (GIOCondition)0; closure->function = function; closure->data = data; if (condition & PURPLE_INPUT_READ) cond = (GIOCondition)(cond|PURPLE_GLIB_READ_COND); if (condition & PURPLE_INPUT_WRITE) cond = (GIOCondition)(cond|PURPLE_GLIB_WRITE_COND); channel = g_io_channel_unix_new(fd); closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, purple_glib_io_invoke, closure, purple_glib_io_destroy); g_io_channel_unref(channel); return closure->result; } string strupper(string s) { for(string::iterator it = s.begin(); it != s.end(); ++it) *it = (char)toupper(*it); return s; } string strlower(string s) { for(string::iterator it = s.begin(); it != s.end(); ++it) *it = (char)tolower(*it); return s; } bool is_ip(const char *ip) { char *ptr = NULL; int i = 0, d = 0; for(; i < 4; ++i) /* 4 dots expected (IPv4) */ { /* Note about strtol: stores in endptr either NULL or '\0' if conversion is complete */ if(!isdigit((unsigned char) *ip) /* most current case (not ip, letter host) */ /* ok, valid number? */ || (d = (int)strtol(ip, &ptr, 10)) < 0 || d > 255 || (ptr && *ptr != 0 && (*ptr != '.' || 3 == i) && ptr != ip)) return false; if(ptr) ip = ptr + 1, ptr = NULL; /* jump the dot */ } return true; } bool check_write_file(string path, string filename) { /* Create the directory if not exist. */ DIR* d; if(!(d = opendir(path.c_str()))) { if(mkdir(path.c_str(), 0700) < 0) return false; } else closedir(d); path += "/" + filename; struct stat st; if (g_stat(path.c_str(), &st) != 0) { /* File doesn't exist. */ const char* dir = g_path_get_dirname(path.c_str()); if(g_access(dir, W_OK) != 0) return false; } else if(S_ISDIR(st.st_mode)) return false; return true; } static struct { const char* num; const char* name; } irc_colors[] = { { "00", "white" }, { "01", "black" }, { "02", "blue" }, { "03", "dark green" }, { "04", "red" }, { "05", "brown" }, { "06", "purple" }, { "07", "orange" }, { "08", "yellow" }, { "09", "green" }, { "10", "teal" }, { "11", "cyan" }, { "12", "light blue" }, { "13", "pink" }, { "14", "grey" }, { "15", "light grey" }, }; /* Stolen from prpl-irc */ gchar* irc2markup(const gchar* string) { const char *cur, *end; char fg[3] = "\0\0", bg[3] = "\0\0"; int fgnum, bgnum; int font = 0, bold = 0, underline = 0, italic = 0; GString *decoded; if (string == NULL) return NULL; decoded = g_string_sized_new(strlen(string)); cur = string; do { char* tmp; end = strpbrk(cur, "\002\003\007\017\026\037"); tmp = g_markup_escape_text(cur, end ? end - cur : -1); decoded = g_string_append(decoded, tmp); g_free(tmp); cur = end ? end : cur + strlen(cur); switch (*cur) { case '\002': cur++; if (!bold) { decoded = g_string_append(decoded, ""); bold = TRUE; } else { decoded = g_string_append(decoded, ""); bold = FALSE; } break; case '\003': cur++; fg[0] = fg[1] = bg[0] = bg[1] = '\0'; if (isdigit(*cur)) fg[0] = *cur++; if (isdigit(*cur)) fg[1] = *cur++; if (*cur == ',') { cur++; if (isdigit(*cur)) bg[0] = *cur++; if (isdigit(*cur)) bg[1] = *cur++; } if (font) { decoded = g_string_append(decoded, ""); font = FALSE; } if (fg[0]) { fgnum = atoi(fg); if (fgnum < 0 || fgnum > 15) continue; font = TRUE; g_string_append_printf(decoded, "= 0 && bgnum < 16) g_string_append_printf(decoded, " BACK=\"%s\"", irc_colors[bgnum].name); } decoded = g_string_append_c(decoded, '>'); } break; case '\011': cur++; if (!italic) { decoded = g_string_append(decoded, ""); italic = TRUE; } else { decoded = g_string_append(decoded, ""); italic = FALSE; } break; case '\037': cur++; if (!underline) { decoded = g_string_append(decoded, ""); underline = TRUE; } else { decoded = g_string_append(decoded, ""); underline = FALSE; } break; case '\007': case '\026': cur++; break; case '\017': cur++; /* fallthrough */ case '\000': if (bold) decoded = g_string_append(decoded, ""); if (italic) decoded = g_string_append(decoded, ""); if (underline) decoded = g_string_append(decoded, ""); if (font) decoded = g_string_append(decoded, ""); break; default: purple_debug(PURPLE_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur); } } while (*cur); return g_string_free(decoded, FALSE); } gchar* markup2irc(const gchar* markup) { char* newline = purple_strdup_withhtml(markup); GString* s = g_string_sized_new(strlen(newline)); gchar *ptr, *next, *buf; for(ptr = newline; *ptr; ptr = next) { next = g_utf8_next_char(ptr); if(*ptr == '<') { bool closed = false; ++ptr; if(*ptr == '/') { closed = true; ++ptr; } while(*next && *next != '>') next = g_utf8_next_char(next); if(*next) next = g_utf8_next_char(next); switch(*ptr) { case 'i': case 'I': if(ptr+2 == next) g_string_append(s, "\037\002"); break; case 'u': case 'U': if(ptr+2 == next) g_string_append_c(s, '\037'); break; case 'B': case 'b': if(ptr+2 == next) g_string_append_c(s, '\002'); else { g_string_append_c(s, '<'); if(closed) g_string_append_c(s, '/'); g_string_append_len(s, ptr, next-ptr); } break; case 'F': case 'f': if(closed && !strncasecmp(ptr, "FONT", 4)) { g_string_append_c(s, '\003'); break; } else if(!closed && !strncasecmp(ptr, "FONT ", 5)) { const char *foreground = NULL, *background = NULL; bool is_foreground; gchar* space; ptr += 5; /* loop on all attributes of the FONT tag to get * IRC colors. */ while(*ptr && *ptr != '>') { unsigned i; if(!strncasecmp(ptr, "color=", 6)) { ptr += 6; if(*ptr == '"') ++ptr; is_foreground = true; } else if(!strncasecmp(ptr, "back=", 5)) { ptr += 5; if(*ptr == '"') ++ptr; is_foreground = false; } else break; space = ptr; while(*space && *space != '"') space = g_utf8_next_char(space); for(i = 0; i < sizeof irc_colors / sizeof *irc_colors; ++i) if(!strncasecmp(ptr, irc_colors[i].name, space-ptr)) { if(is_foreground) foreground = irc_colors[i].num; else background = irc_colors[i].num; break; } if(*space == '"') ++space; ptr = space; while(*ptr && *ptr == ' ') ptr = g_utf8_next_char(ptr); } if(foreground) { g_string_append_c(s, '\003'); g_string_append(s, foreground); if(background) { g_string_append_c(s, ','); g_string_append(s, background); } } break; } default: g_string_append_c(s, '<'); if(closed) g_string_append_c(s, '/'); g_string_append_len(s, ptr, next-ptr); continue; } } else g_string_append_len(s, ptr, next-ptr); } g_free(newline); /* Strip all other html tags */ newline = g_string_free(s, FALSE); buf = purple_markup_strip_html(newline); g_free(newline); return buf; } minbif-1.0.5+git20120508/src/core/mutex.h0000644000175000017500000000350011754151773015531 0ustar sebseb/* * Copyright(C) 2008 Laurent Defert, Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * This product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim * Hudson (tjh@cryptsoft.com). * * */ #ifndef MUTEX_H #define MUTEX_H #include #include enum MutexType { INVALID_MUTEX, NORMAL_MUTEX, RECURSIVE_MUTEX }; class Mutex { private: pthread_mutex_t* mutex; MutexType type; void Init(enum MutexType _type); public: class MutexError : public std::exception {}; Mutex(enum MutexType _type = NORMAL_MUTEX); Mutex(const Mutex &m); Mutex& operator=(const Mutex& m); ~Mutex(); void Lock() const; void Unlock() const; }; /** Lock a Mutex locally. * * On a block, juste create an auto instance of this class. * When it will be removed (while exiting this block), mutex will be unlocked. * * Juste use: * BlockLockMutex lock(mutex); * */ class BlockLockMutex { const Mutex* p; BlockLockMutex(const BlockLockMutex&); BlockLockMutex& operator=(const BlockLockMutex&); public: explicit BlockLockMutex(const Mutex* _p) : p(_p) { p->Lock(); } ~BlockLockMutex() { p->Unlock(); } }; #endif minbif-1.0.5+git20120508/src/core/callback.cpp0000644000175000017500000000262311754151773016463 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "callback.h" #include "log.h" static bool _callback(void* data) { _CallBack* cb; if(!data || !(cb = static_cast<_CallBack*>(data))) { b_log[W_ERR] << "g_callback() handled with non CallBack instance"; return false; } return cb->run(); } void g_callback_input(void* data, gint src, PurpleInputCondition i) { _callback(data); } gboolean g_callback(void* data) { return _callback(data); } gboolean g_callback_delete(void* data) { _CallBack* cb; if(!data || !(cb = static_cast<_CallBack*>(data))) { b_log[W_ERR] << "g_callback() handled with non CallBack instance"; return false; } bool ret = cb->run(); if(!ret) delete cb; return ret; } minbif-1.0.5+git20120508/src/core/config.h0000644000175000017500000004261411754151773015645 0ustar sebseb/* config.h - This is a library to make a configuration * * Copyright (C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* !WARNING! This library must be used in a program with this function : * * std::string stringtok(std::string &in, const char * const delimiters); */ #ifndef PF_CONFIG_H #define PF_CONFIG_H #include #include #include #include #include #include class ConfigSection; class ConfigItem; typedef std::multimap SectionMap; typedef std::map ItemMap; /******************************************************************************************** * Config * ********************************************************************************************/ /** @page MyConfig Use MyConfig classes * * This library can be used to read a configuration in this form : * *
 * section {
 *      label = value
 *      subsection {
 *           subsection2 {
 *                 label = value
 *                 label2 = value2
 *           }
 *           label = value
 *      }
 * }
 *
 * section2 {
 *       ...
 * }
 *
* * You have to create an instance of class \b MyConfig . * Next, define all sections, subsections and items. If there is an error, a MyConfig::error exception is sended. * Then, try to load configuration. * * \b ConfigSection is class for a section in configuration. Add one section in MyConfig or in an other * ConfigSection (his parent). * * There is a few types of item classes. Each class is derived from \b ConfigItem and have some features * about his type, condition to a valid value, etc. * * \b ConfigItem Has some functions to return a valid in a few types. This functions are \b String(), \b Integer() * and \b Boolean(). * * You can create your own derived class, read \b ConfigItem interface and a derived class from \b ConfigItem to * know what you have to do. Existing derived classed are \b ConfigItem_string, \b ConfigItem_int and * \b ConfigItem_bool. * * There is an example : * *
 * // Creation of a new instance of MyConfig. It will read "app.cfg".
 * MyConfig* config = new MyConfig("app.cfg');
 *
 * // Use of a try/catch bloc, to print an error if we catch an exception
 * try
 * {
 *    // This first section is a no-forkable section
 *    ConfigSection* section = config->AddSection("section", "this is my first section", MyConfig::NORMAL);
 *    // We add a new item. His value must be a string
 *    section->AddItem(new ConfigItem_string("str", "a string item"));
 *    // An integer item
 *    section->AddItem(new ConfigItem_int("int", "an integer item"));
 *    // We add a forkable subsection.
 *    section = section->AddSection("subsection", "a subsection of my first section", MyConfig::MULTIPLE);
 *    // This item has "is_name" true. So his value will be the identificator of this fork
 *    section->AddItem(new ConfigItem_string("name", "name of the subsection"), true);
 *    section->AddItem(new ConfigItem_string("description", "description of this subsection"));
 *    // We return to parent section ("section")
 *    section = section->Parent();
 *    // A new subsection, no-forkable
 *    section = section->AddSection("subsection2", "a second subsection", MyConfig::NORMAL);
 *    section->AddItem(new ConfigItem_int("something", "i don't know"));
 *    section = config->AddSection("section2", "my second head-section (optional)", MyConfig::OPTIONAL);
 *    // Here we suppose there is a ConfigItem_ip type. We can imagine that ConfigItem_ip::SetValue() will
 *    // check if value is a real ip.
 *    section->AddItem(new ConfigItem_ip("ip", "ip of a server"));
 *
 *    // Now we can load configuration, and pray there aren't any error.
 *    // This function will not send any exception, but if there is any error in configuration, it will
 *    // print messages (each times) and return false.
 *    return config->Load();
 * }
 * catch(MyConfig::error &e)
 * {
 *    std::cerr << "I catch an error from MyConfig : " << e.Reason() << std::endl;
 *    delete config;
 *    return false;
 * }
 * 
* */ class MyConfig { public: MyConfig(std::string path); MyConfig(); ~MyConfig(); /** This class is an exception class throwed when there is a problem in building of sections/items */ class error_exc { public: error_exc(std::string _reason) : reason(_reason) {} std::string Reason() const { return reason; } private: std::string reason; }; enum section_t { NORMAL, MULTIPLE, OPTIONAL }; bool Load(std::string _path = ""); bool FindEmpty(); /** Get a section from his label */ ConfigSection* GetSection(std::string label); /** Get a section fork with his label and his name. * This function works only with a section marcked "multiple". It will return the fork of this section. * @param label this is the label of the section * @param name name of the section */ ConfigSection* GetSection(std::string label, std::string name); /** Get clones of a section marcked "multiple" */ std::vector GetSectionClones(std::string label); void Clean(); /** Add a head-section in the configuration. * Before loading configuration, you have to define all sections and items. This function must be * used to add a head-section. * @param label section's label, this is on the identificator in the configuration ("label {") * @param description section's description, sended to user when this section is forgotten * @param multiple if setted, this section can have multiple definitions (see ConfigSection::AddItem) */ ConfigSection* AddSection(std::string label, std::string description, section_t type) throw(error_exc); unsigned NbLines() const { return line_count; } std::string Path() const { return path; } private: ConfigSection* AddSection(ConfigSection*); std::string path; bool loaded; SectionMap sections; unsigned int line_count; }; /******************************************************************************************** * ConfigSection * ********************************************************************************************/ class ConfigSection { public: ConfigSection(ConfigSection&); ~ConfigSection(); friend class MyConfig; /** Get a subsection from his label */ ConfigSection* GetSection(std::string label); /** Get a subsection fork with his label and his name. * This function works only with a section marked "multiple". It will return the fork of this section * whose have the searched name. * @param label this is the label of the subsection * @param name name of the subsection */ ConfigSection* GetSection(std::string label, std::string name); /** Get clones of a section marked "multiple" */ std::vector GetSectionClones(std::string label); /** Add a subsection in the configuration. * Before loading configuration, you have to define all sections and items. This function must be * used to add a subsection. * @param label section's label, this is on the identificator in the configuration ("label {") * @param description section's description, sended to user when this section is forgotten * @param multiple if setted, this section can have multiple definitions (see ConfigSection::AddItem) */ ConfigSection* AddSection(std::string label, std::string description, MyConfig::section_t type) throw(MyConfig::error_exc); void Clean(); /** Get an item from his label */ ConfigItem* GetItem(std::string label); /** This function add an item in this section. * If this item is a member of a multiple section (see ConfigSection::AddSection), an item have to be an * identificator of this section. Set "is_name" if this item is one. * @param item this is a new item of a derived type from ConfigItem. It will be deleted if there is an exception * @param is_name if true, this is the name (identificator) of the section. */ void AddItem(ConfigItem* item, bool is_name = false) throw(MyConfig::error_exc); /** Is this is a multiple struct, name is an identificator of this type of struct in the configuration */ std::string Name() const { return name; } void SetName(std::string n) { name = n; } /** Label is an identificator in the configuration « label { }; » */ std::string Label() const { return label; } std::string Description() const { return description; } /** If this is true, this section can be forked */ bool IsMultiple() const { return type == MyConfig::MULTIPLE; } bool IsOptional() const { return type == MyConfig::OPTIONAL || type == MyConfig::MULTIPLE; } ConfigSection* Parent() const { return parent; } bool IsCopy() const { return copy; } void SetCopy() { copy = true; } /** NameItem is name's item of this section */ ConfigItem* NameItem() const { return name_item; } /** If true, this item is found in the configuration */ bool Found() const { return found; } void SetFound(bool f = true) { found = f; } private: /** This constructor is privated because only AddSection must call him. */ ConfigSection(std::string name, std::string description, MyConfig::section_t type, MyConfig* config, ConfigSection* parent); bool FindEmpty(); ConfigSection* AddSection(ConfigSection*); std::string name; std::string label; std::string description; MyConfig::section_t type; ItemMap items; SectionMap sections; MyConfig* config; ConfigSection* parent; bool copy; ConfigItem* name_item; bool found; }; /******************************************************************************************** * ConfigItem * ********************************************************************************************/ /** This is an abstract class whose represent an Item in pair \a label=value . * You have to create a derived class from this class to have a new type. * Overload « Integer() » and « String() » to return value in each forms. * Overload « SetValue(std::string) » to check value and cast to type. */ class ConfigItem { /* Constructeur */ public: typedef void (*TCallBack) (ConfigItem*); /** \b ConfigItem is a section's item. * @param _label this is the identificator of the item (in pair \a label=value ) * @param _description description of this item * @param _def_value default value, if isn't setted, this item is needed * @param _call_back this is a pointer to a function called before setting this item * @param _config a pointer to the MyConfig instance * @param _parent parent of this item (a ConfigSection) */ ConfigItem(std::string _label, std::string _description, std::string _def_value, TCallBack _call_back, MyConfig* _config, ConfigSection* _parent) : label(_label), description(_description), config(_config), parent(_parent), found(false), call_back(_call_back), def_value(_def_value) {} virtual ~ConfigItem() {} friend class ConfigSection; /** Give a clone of this Item (you couldn't be allowed to call this) */ virtual ConfigItem* Clone() const = 0; std::string Label() const { return label; } std::string Description() const { return description; } ConfigSection* Parent() const { return parent; } MyConfig* GetConfig() const { return config; } /** If true, this item was found on the configuration by parser */ bool Found() const { return found; } void SetFound(bool f = true) { found = f; } virtual int Integer() const /**< Get value as an integer */ { return 0; } virtual std::string String() const/**< Get value as a string */ { return ""; } virtual bool Boolean() const /**< Get a boolean value */ { return false; } virtual int MinInteger() const { return 0; } virtual int MaxInteger() const { return 0; } /**< This function might return false if value isn't good */ virtual bool SetValue(std::string) = 0; virtual std::string ValueType() const = 0; TCallBack CallBack() const { return call_back; } std::string DefValue() const { return def_value; } private: std::string label; std::string description; MyConfig* config; ConfigSection* parent; bool found; TCallBack call_back; std::string def_value; }; /** This is a derived class from ConfigItem whose represent a string item */ class ConfigItem_string : public ConfigItem { public: ConfigItem_string(std::string _label, std::string _description, std::string def_value = "", TCallBack cb = 0, MyConfig* _config = 0, ConfigSection* _parent = 0) : ConfigItem(_label, _description, def_value, cb, _config, _parent) {} virtual ConfigItem* Clone() const { return new ConfigItem_string(Label(), Description(), DefValue(), CallBack(), GetConfig(), Parent()); } virtual std::string String() const { return value; } virtual bool SetValue(std::string s) { value = s; return true; } std::string ValueType() const { return "string"; } private: std::string value; }; /** This is a derived class from ConfigItem whose represent an integer item */ class ConfigItem_int : public ConfigItem { public: ConfigItem_int(std::string _label, std::string _description, int _min = INT_MIN, int _max = INT_MAX, std::string def_value = "", TCallBack cb = 0, MyConfig* _config = 0, ConfigSection* _parent = 0) : ConfigItem(_label, _description, def_value, cb, _config, _parent), min(_min), max(_max) {} virtual ConfigItem* Clone() const { return new ConfigItem_int(Label(), Description(), min, max, DefValue(), CallBack(), GetConfig(), Parent()); } /** We return a string form of this integer */ virtual std::string String() const { std::ostringstream oss; oss << value; return oss.str(); } virtual int Integer() const { return value; } virtual bool SetValue(std::string s) { for(std::string::const_iterator it = s.begin(); it != s.end(); ++it) if(!isdigit(*it)) return false; std::istringstream(s) >> value; return (value >= min && value <= max); } std::string ValueType() const { if(min == INT_MIN) return "integer"; else { std::ostringstream off; std::string in, ax; off << min; in = off.str(); off << max; ax = off.str(); return "integer (between " + in + " and " + ax + ")"; } } protected: int value, min, max; }; /** This is a derived class from ConfigItem whose represent an integer range item */ class ConfigItem_intrange : public ConfigItem_int { public: ConfigItem_intrange(std::string _label, std::string _description, int _min = INT_MIN, int _max = INT_MAX, std::string def_value = "", TCallBack cb = 0, MyConfig* _config = 0, ConfigSection* _parent = 0) : ConfigItem_int(_label, _description, _min, _max, def_value, cb, _config, _parent) {} virtual ConfigItem* Clone() const { return new ConfigItem_intrange(Label(), Description(), min, max, DefValue(), CallBack(), GetConfig(), Parent()); } /** We return a string form of this integer */ virtual std::string String() const { std::ostringstream oss; oss << value_min << "-" << value_max; return oss.str(); } virtual int MinInteger() const { return value_min; } virtual int MaxInteger() const { return value_max; } virtual bool SetValue(std::string s) { std::string min_s, max_s; bool on_min = true; for(std::string::const_iterator it = s.begin(); it != s.end(); ++it) { if(isdigit(*it)) { if(on_min) min_s += *it; else max_s += *it; } else if(on_min && (*it == ':' || *it == '-')) on_min = false; else return false; } std::istringstream(min_s) >> value_min; std::istringstream(max_s) >> value_max; return (value_min >= min && value_max >= value_min && value_max <= max); } std::string ValueType() const { std::ostringstream off, off2; std::string in, ax; off << min; in = off.str(); off2 << max; ax = off2.str(); return "integer range (between " + in + " and " + ax + ")"; } private: int value_min, value_max; }; /** This is a derived class from ConfigItem whose represent an integer item */ class ConfigItem_bool : public ConfigItem { public: ConfigItem_bool(std::string _label, std::string _description, std::string def_value = "", TCallBack cb = 0, MyConfig* _config = 0, ConfigSection* _parent = 0) : ConfigItem(_label, _description, def_value, cb, _config, _parent) {} virtual ConfigItem* Clone() const { return new ConfigItem_bool(Label(), Description(), DefValue(), CallBack(), GetConfig(), Parent()); } /** We return a string form of this integer */ virtual std::string String() const { return (value ? "true" : "false"); } virtual int Integer() const { return value; } virtual bool Boolean() const { return value; } virtual bool SetValue(std::string s) { if(s == "true" || s == "on" || s == "yes") value = true; else if(s == "false" || s == "off" || s == "no") value = false; else return false; return true; } std::string ValueType() const { return "boolean ('true' or 'false')"; } private: bool value; }; extern MyConfig conf; #endif /* LIBCONFIG_H */ minbif-1.0.5+git20120508/src/core/minbif.h0000644000175000017500000000233311754151773015636 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MINBIF_H #define MINBIF_H #include #include "config.h" using std::string; class ServerPoll; struct _GMainLoop; class Minbif { struct _GMainLoop *loop; ServerPoll* server_poll; string pidfile; void add_server_block_common_params(ConfigSection* section); void usage(int argc, char** argv); void version(void); void remove_pidfile(void); public: Minbif(); ~Minbif(); int main(int argc, char** argv); void rehash(); void quit(); }; #endif /* MINBIF_H */ minbif-1.0.5+git20120508/src/core/callback.h0000644000175000017500000000275411754151773016135 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CALLBACK_H #define CALLBACK_H #include class _CallBack { public: virtual ~_CallBack() {} virtual bool run() = 0; virtual void setObj(void*) = 0; }; template class CallBack : public _CallBack { public: typedef bool (T::*TFunc) (void*); CallBack(T* _obj, TFunc _func, void* _data = 0) : obj(_obj), func(_func), data(_data) {} virtual bool run() { return (obj->*func) (data); } virtual void setObj(void* o) { obj = static_cast(o); } private: T* obj; TFunc func; void *data; }; gboolean g_callback(void* data); gboolean g_callback_delete(void* data); void g_callback_input(void* data, gint n = 0, PurpleInputCondition input = (PurpleInputCondition)0); #endif /* CALLBACK_H */ minbif-1.0.5+git20120508/src/core/sighandler.h0000644000175000017500000000231511754151773016512 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SIGHANDLER_H #define SIGHANDLER_H class Minbif; class SigHandler { Minbif* app; /** Handler called by libc */ static void handler(int r); bool rehash(void*); /**< rehash callback */ bool quit(void*); /**< quit callback */ public: SigHandler(); ~SigHandler(); /** Assign an application object to the sighandler. * @param app Minbif instance. */ void setApplication(Minbif* app); }; extern SigHandler sighandler; #endif /* SIGHANDLER_H */ minbif-1.0.5+git20120508/src/core/sighandler.cpp0000644000175000017500000000450111754151773017044 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "sighandler.h" #include "minbif.h" #include "callback.h" #include "util.h" SigHandler sighandler; SigHandler::SigHandler() : app(NULL) { } SigHandler::~SigHandler() { } void SigHandler::setApplication(Minbif* app) { this->app = app; struct sigaction sig, old; memset( &sig, 0, sizeof( sig ) ); sig.sa_handler = &SigHandler::handler; sigaction(SIGCHLD, &sig, &old); sigaction(SIGPIPE, &sig, &old); sigaction(SIGHUP, &sig, &old); sig.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sig, &old); sigaction(SIGILL, &sig, &old); sigaction(SIGBUS, &sig, &old); sigaction(SIGFPE, &sig, &old); sigaction(SIGSEGV, &sig, &old); sigaction(SIGTERM, &sig, &old); sigaction(SIGQUIT, &sig, &old); sigaction(SIGXCPU, &sig, &old); } bool SigHandler::rehash(void*) { app->rehash(); return false; } bool SigHandler::quit(void*) { app->quit(); return false; } void SigHandler::handler(int r) { /* A signal handler MUST NOT take time, and call any else function * than g_timeout*. This is because we are in an unsafe state, as * the stack is interromped. */ switch(r) { case SIGCHLD: { pid_t pid; int st; while((pid = waitpid(0, &st, WNOHANG)) > 0) ; break; } case SIGPIPE: break; case SIGHUP: g_timeout_add(0, g_callback_delete, new CallBack(&sighandler, &SigHandler::rehash)); break; case SIGTERM: g_timeout_add(0, g_callback_delete, new CallBack(&sighandler, &SigHandler::quit)); break; default: /* This signal is not catched by minbif, so raise it. */ raise(r); } } minbif-1.0.5+git20120508/src/core/minbif.cpp0000644000175000017500000002212711754151773016174 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2011 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include "minbif.h" #include "sighandler.h" #include "version.h" #include "log.h" #include "util.h" #include "im/im.h" #include "server_poll/poll.h" const char* infotxt[] = { MINBIF_VERSION, strdup(MINBIF_BUILD.c_str()), /* don't care if we don't free it as it can be used until exit. */ "", "Copyright(C) 2009-2011 Romain Bignon", "This program is free software; you can redistribute it and/or modify", "it under the terms of the GNU General Public License as published by", "the Free Software Foundation, version 2 of the License.", NULL }; Minbif::Minbif() : loop(NULL), server_poll(0) { ConfigSection* section; ConfigSection* sub; section = conf.AddSection("path", "Path information", MyConfig::NORMAL); section->AddItem(new ConfigItem_string("users", "Users directory")); section->AddItem(new ConfigItem_string("motd", "Path to motd", " ")); section = conf.AddSection("irc", "Server information", MyConfig::NORMAL); section->AddItem(new ConfigItem_string("hostname", "Server hostname", " ")); section->AddItem(new ConfigItem_string("password", "Global server password", " ")); section->AddItem(new ConfigItem_int("type", "Type of daemon", 0, 2, "0")); section->AddItem(new ConfigItem_int("ping", "Ping frequence (s)", 0, 65535, "60")); section->AddItem(new ConfigItem_string("buddy_icons_url", "URL to display in /WHOIS to get a buddy icon", " ")); sub = section->AddSection("inetd", "Inetd information", MyConfig::OPTIONAL); add_server_block_common_params(sub); sub = section->AddSection("daemon", "Daemon information", MyConfig::OPTIONAL); sub->AddItem(new ConfigItem_string("bind", "IP address to listen on")); sub->AddItem(new ConfigItem_int("port", "Port to listen on", 1, 65535), true); sub->AddItem(new ConfigItem_bool("background", "Start minbif in background", "true")); sub->AddItem(new ConfigItem_int("maxcon", "Maximum simultaneous connections", 0, 65535, "0")); add_server_block_common_params(sub); sub = section->AddSection("oper", "Define an IRC operator", MyConfig::MULTIPLE); sub->AddItem(new ConfigItem_string("login", "Nickname of IRC operator"), true); sub->AddItem(new ConfigItem_string("password", "IRC operator password")); sub->AddItem(new ConfigItem_string("email", "IRC operator email address", "*@*")); section = conf.AddSection("aaa", "Authentication, Authorization and Accounting", MyConfig::OPTIONAL); section->AddItem(new ConfigItem_bool("use_local", "Use local database to authenticate users", "true")); #ifdef HAVE_PAM section->AddItem(new ConfigItem_bool("use_pam", "Use PAM mechanisms to authenticate/authorize users", "false")); section->AddItem(new ConfigItem_bool("pam_setuid", "Child process setuid with the pam user (needs root and pam auth)", "false")); #endif section->AddItem(new ConfigItem_bool("use_connection", "Use connection information to authenticate/authorize users", "false")); section = conf.AddSection("file_transfers", "File transfers parameters", MyConfig::OPTIONAL); section->AddItem(new ConfigItem_bool("enabled", "Enable file transfers", "true")); section->AddItem(new ConfigItem_bool("dcc", "Send files to IRC user with DCC", "true")); section->AddItem(new ConfigItem_string("dcc_own_ip", "Force minbif to always send DCC requests from a particular IP address", " ")); section->AddItem(new ConfigItem_intrange("port_range", "Port range to listen on for DCC", 1024, 65535, "1024-65535")); section = conf.AddSection("logging", "Log information", MyConfig::NORMAL); section->AddItem(new ConfigItem_string("level", "Logging level")); section->AddItem(new ConfigItem_bool("to_syslog", "Log error and warnings to syslog")); section->AddItem(new ConfigItem_bool("conv_logs", "Enable conversation logging", "false")); } void Minbif::add_server_block_common_params(ConfigSection* section) { section->AddItem(new ConfigItem_string("security", "none/tls/starttls/starttls-mandatory", "none")); #ifdef HAVE_TLS ConfigSection* sub = section->AddSection("tls", "TLS information", MyConfig::OPTIONAL); sub->AddItem(new ConfigItem_string("trust_file", "CA certificate file for TLS", " ")); sub->AddItem(new ConfigItem_string("crl_file", "CA certificate file for TLS", " ")); sub->AddItem(new ConfigItem_string("cert_file", "Server certificate file for TLS")); sub->AddItem(new ConfigItem_string("key_file", "Server key file for TLS")); sub->AddItem(new ConfigItem_string("priority", "Priority list for ciphers, exchange methods, macs and compression methods", "NORMAL")); #endif } Minbif::~Minbif() { delete server_poll; remove_pidfile(); } void Minbif::remove_pidfile(void) { if(pidfile.empty()) return; std::ifstream fp(pidfile.c_str()); if(!fp) return; int i; fp >> i; if(i != getpid()) return; unlink(pidfile.c_str()); } void Minbif::usage(int argc, char** argv) { std::cerr << "Usage: " << argv[0] << " [OPTIONS]... CONFIG_PATH" << std::endl << std::endl; std::cerr << "Options:" << std::endl; std::cerr << " -h, --help Display this notice" << std::endl; std::cerr << " -v, --version Version of minbif" << std::endl; std::cerr << " -m, --mode=MODE Mode to run (MODE is a number)" << std::endl; std::cerr << " -p, --pidfile=PIDFILE Path to pid file" << std::endl; } void Minbif::version(void) { for(size_t i = 0; infotxt[i] != NULL; ++i) std::cout << infotxt[i] << std::endl; } int Minbif::main(int argc, char** argv) { static struct option long_options[] = { { "pidfile", 1, NULL, 'p' }, { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'v' }, { "mode", 1, NULL, 'm' }, { NULL, 0, NULL, 0 } }; int option_index = 0, c; int mode = -1; while((c = getopt_long(argc, argv, "m:p:hv", long_options, &option_index)) != -1) switch(c) { case 'm': mode = atoi(optarg); break; case 'h': usage(argc, argv); return EXIT_SUCCESS; break; case 'v': version(); return EXIT_SUCCESS; break; case 'p': { std::ifstream fi(optarg); if(fi) { std::cerr << "It seems that minbif is already launched. Perhaps try to erase file " << optarg << std::endl; fi.close(); return EXIT_FAILURE; } pidfile = optarg; break; } default: return EXIT_FAILURE; break; } if(optind >= argc) { std::cerr << argv[0] << ": wrong argument count" << std::endl; usage(argc, argv); return EXIT_FAILURE; } try { struct rlimit rlim; if(!getrlimit(RLIMIT_CORE, &rlim) && rlim.rlim_cur != RLIM_INFINITY) { rlim.rlim_cur = RLIM_INFINITY; rlim.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_CORE, &rlim); } if(!conf.Load(argv[optind])) { b_log[W_ERR] << "Unable to load configuration, exiting.."; return EXIT_FAILURE; } b_log.setLoggedFlags(conf.GetSection("logging")->GetItem("level")->String(), conf.GetSection("logging")->GetItem("to_syslog")->Boolean()); /* Set users directory path and if I have rights to write in. */ im::IM::setPath(conf.GetSection("path")->GetItem("users")->String()); if (mode < 0) mode = conf.GetSection("irc")->GetItem("type")->Integer(); server_poll = ServerPoll::build((ServerPoll::poll_type_t)mode, this); b_log.setServerPoll(server_poll); if(!pidfile.empty()) { std::ofstream fo(pidfile.c_str()); if(!fo) { std::cerr << "Unable to create file '" << pidfile << "': " << strerror(errno) << std::endl; return EXIT_FAILURE; } fo << getpid() << std::endl; fo.close(); } sighandler.setApplication(this); g_thread_init(NULL); loop = g_main_new(FALSE); g_main_run(loop); return EXIT_SUCCESS; } catch(MyConfig::error_exc &e) { b_log[W_ERR] << "Error while loading:"; b_log[W_ERR] << e.Reason(); } catch(im::IMError &e) { b_log[W_ERR] << "Unable to load IM settings: " << e.Reason(); } catch(ServerPollError &e) { b_log[W_ERR] << "Unable to load the server poll."; } return EXIT_FAILURE; } void Minbif::rehash() { if(!conf.Load()) { b_log[W_ERR] << "Unable to load configuration, exiting.."; quit(); } b_log.setLoggedFlags(conf.GetSection("logging")->GetItem("level")->String(), conf.GetSection("logging")->GetItem("to_syslog")->Boolean()); if(server_poll) server_poll->rehash(); } void Minbif::quit() { g_main_quit(loop); } int main(int argc, char** argv) { Minbif minbif; return minbif.main(argc, argv); } minbif-1.0.5+git20120508/src/sockwrap/0000755000175000017500000000000011754151773015121 5ustar sebsebminbif-1.0.5+git20120508/src/sockwrap/sockwrap_plain.h0000644000175000017500000000212211754151773020303 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sockwrap.h" #ifndef PF_SOCKWRAP_PLAIN_H #define PF_SOCKWRAP_PLAIN_H namespace sock { class SockWrapperPlain : public SockWrapper { public: SockWrapperPlain(ConfigSection* config, int _recv_fd, int _send_fd); ~SockWrapperPlain(); string Read(); void Write(string s); }; }; #endif /* PF_SOCKWRAP_PLAIN_H */ minbif-1.0.5+git20120508/src/sockwrap/sockwrap_tls.cpp0000644000175000017500000001625611754151773020352 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sockwrap_tls.h" #include "sock.h" #include #include #include "gnutls/x509.h" namespace sock { static void tls_debug_message(int level, const char* message) { b_log[W_SOCK] << "TLS debug: " << message; } SockWrapperTLS::SockWrapperTLS(ConfigSection* _config, int _recv_fd, int _send_fd) : SockWrapper(_config, _recv_fd, _send_fd) { tls_ok = false; trust_check = false; ConfigSection* c_section = getConfig()->GetSection("tls"); if (!c_section->Found()) throw TLSError("Missing section /tls"); /* GNUTLS init */ b_log[W_SOCK] << "Initializing GNUTLS"; tls_err = gnutls_global_init(); CheckTLSError(); /* GNUTLS logging */ b_log[W_SOCK] << "Setting up GNUTLS logging"; gnutls_global_set_log_function(tls_debug_message); gnutls_global_set_log_level(10); b_log[W_SOCK] << "Setting up GNUTLS certificates"; tls_err = gnutls_certificate_allocate_credentials(&x509_cred); CheckTLSError(); string trust_file = c_section->GetItem("trust_file")->String(); if (trust_file != " ") { tls_err = gnutls_certificate_set_x509_trust_file(x509_cred, trust_file.c_str(), GNUTLS_X509_FMT_PEM); if (tls_err == GNUTLS_E_SUCCESS) throw TLSError("trust file is empty or does not contain any valid CA certificate"); else if (tls_err < 0) CheckTLSError(); trust_check = true; } string crl_file = c_section->GetItem("crl_file")->String(); if (trust_check && crl_file != " ") { tls_err = gnutls_certificate_set_x509_crl_file(x509_cred, crl_file.c_str(), GNUTLS_X509_FMT_PEM); if (tls_err == GNUTLS_E_SUCCESS) b_log[W_WARNING] << "trust file is empty or does not contain any valid CA certificate"; else if (tls_err < 0) CheckTLSError(); } tls_err = gnutls_certificate_set_x509_key_file(x509_cred, c_section->GetItem("cert_file")->String().c_str(), c_section->GetItem("key_file")->String().c_str(), GNUTLS_X509_FMT_PEM); CheckTLSError(); b_log[W_SOCK] << "Setting up GNUTLS DH params"; tls_err = gnutls_dh_params_init(&dh_params); CheckTLSError(); tls_err = gnutls_dh_params_generate2(dh_params, 1024); CheckTLSError(); gnutls_certificate_set_dh_params(x509_cred, dh_params); b_log[W_SOCK] << "Setting up GNUTLS session"; tls_err = gnutls_init(&tls_session, GNUTLS_SERVER); CheckTLSError(); tls_err = gnutls_priority_set_direct(tls_session, c_section->GetItem("priority")->String().c_str(), NULL); if (tls_err == GNUTLS_E_INVALID_REQUEST) throw TLSError("syntax error in tls_priority parameter"); CheckTLSError(); tls_err = gnutls_credentials_set(tls_session, GNUTLS_CRD_CERTIFICATE, x509_cred); CheckTLSError(); gnutls_transport_set_ptr2(tls_session, (gnutls_transport_ptr_t) recv_fd, (gnutls_transport_ptr_t) send_fd); if (trust_check) { /* client auth would need GNUTLS_CERT_REQUIRE but check is enforced in GetClientUsername() instead */ gnutls_certificate_server_set_request(tls_session, GNUTLS_CERT_REQUEST); } ProcessTLSHandshake(); b_log[W_SOCK] << "SSL connection initialized"; tls_ok = true; } void SockWrapperTLS::ProcessTLSHandshake() { b_log[W_SOCK] << "Starting GNUTLS handshake"; tls_handshake = false; tls_err = -1; while (tls_err != GNUTLS_E_SUCCESS) { tls_err = gnutls_handshake (tls_session); if (gnutls_error_is_fatal(tls_err)) { b_log[W_SOCK] << "TLS handshake failed: " << gnutls_strerror(tls_err); throw TLSError("TLS initialization failed"); } if (tls_err != GNUTLS_E_SUCCESS) usleep(100); } tls_handshake = true; } void SockWrapperTLS::CheckTLSError() { if (tls_err != GNUTLS_E_SUCCESS) throw TLSError(gnutls_strerror(tls_err)); } void SockWrapperTLS::EndSessionCleanup() { sock_ok = false; SockWrapper::EndSessionCleanup(); if (tls_handshake && tls_ok) gnutls_bye (tls_session, GNUTLS_SHUT_WR); tls_ok = false; gnutls_deinit(tls_session); gnutls_certificate_free_credentials(x509_cred); gnutls_global_deinit(); } string SockWrapperTLS::Read() { static char buf[1024]; string sbuf = ""; ssize_t r = GNUTLS_E_AGAIN; if (!sock_ok || !tls_ok || !tls_handshake) return ""; while (tlserr_again(r)) { r = gnutls_record_recv(tls_session, buf, sizeof buf - 1); if (r <= 0) { sock_ok = false; if (r == 0) { tls_ok = false; throw SockError("Connection reset by peer..."); } else if (gnutls_error_is_fatal(r)) { tls_err = r; CheckTLSError(); } else if (r == GNUTLS_E_REHANDSHAKE) { ProcessTLSHandshake(); return ""; } else usleep(100); } else { buf[r] = 0; sbuf = buf; } } return sbuf; } void SockWrapperTLS::Write(string s) { ssize_t r = GNUTLS_E_AGAIN; if (!sock_ok || !tls_ok || !tls_handshake) return; while (tlserr_again(r)) { r = gnutls_record_send(tls_session, s.c_str(), s.size()); if (r <= 0) { sock_ok = false; if (r == 0) { tls_ok = false; throw SockError("Connection reset by peer..."); } else if (gnutls_error_is_fatal(r)) { tls_err = r; CheckTLSError(); } else usleep(100); } } } string SockWrapperTLS::GetClientUsername() { const gnutls_datum_t *cert_list; unsigned int cert_list_size = 0; gnutls_x509_crt_t cert; unsigned int status; static char buf[4096]; size_t size; string dn, username = ""; if (!trust_check || !tls_ok) return ""; b_log[W_INFO] << "Looking for Client Username"; /* accept X.509 certificates only */ if (gnutls_certificate_type_get(tls_session) != GNUTLS_CRT_X509) { b_log[W_INFO] << "TLS Client Certificate is not of X.509 type"; return ""; } cert_list = gnutls_certificate_get_peers(tls_session, &cert_list_size); if (cert_list_size <= 0) { b_log[W_INFO] << "No TLS Client Certificate provided"; return ""; } tls_err = gnutls_certificate_verify_peers2(tls_session, &status); CheckTLSError(); if (status != 0) { b_log[W_INFO] << "TLS Client Certificate not valid"; return ""; } tls_err = gnutls_x509_crt_init(&cert); CheckTLSError(); tls_err = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER); if (tls_err == GNUTLS_E_SUCCESS) { size = sizeof(buf); tls_err = gnutls_x509_crt_get_dn(cert, buf, &size); dn = buf; size_t pos1, pos2; pos1 = dn.find("CN="); if (pos1 != string::npos) { pos2 = dn.find(",", pos1); if (pos2 != string::npos) username = dn.substr(pos1 + 3, pos2 - pos1 - 3); } } gnutls_x509_crt_deinit(cert); CheckTLSError(); if (username.empty()) b_log[W_INFO] << "Client Username not found"; else b_log[W_INFO] << "Client Username: " << username; return username; } }; minbif-1.0.5+git20120508/src/sockwrap/sockwrap_tls.h0000644000175000017500000000307711754151773020014 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sockwrap.h" #include #ifndef PF_SOCKWRAP_TLS_H #define PF_SOCKWRAP_TLS_H #define tlserr_again(tls_err) (tls_err == GNUTLS_E_INTERRUPTED || tls_err == GNUTLS_E_AGAIN) namespace sock { class TLSError : public SockError { public: TLSError(string _reason) : SockError("[TLS] " + _reason) {} }; class SockWrapperTLS : public SockWrapper { gnutls_certificate_credentials_t x509_cred; gnutls_dh_params_t dh_params; gnutls_session_t tls_session; bool tls_handshake; bool tls_ok; bool trust_check; int tls_err; void EndSessionCleanup(); void ProcessTLSHandshake(); void CheckTLSError(); public: SockWrapperTLS(ConfigSection* config, int _recv_fd, int _send_fd); string Read(); void Write(string s); virtual string GetClientUsername(); }; }; #endif /* PF_SOCKWRAP_TLS_H */ minbif-1.0.5+git20120508/src/sockwrap/sockwrap.cpp0000644000175000017500000000661511754151773017466 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "sockwrap.h" #include "sockwrap_plain.h" #ifdef HAVE_TLS # include "sockwrap_tls.h" #endif #include "core/util.h" namespace sock { SockWrapper::SockWrapper(ConfigSection* _config, int _recv_fd, int _send_fd) : config(_config), recv_fd(_recv_fd), send_fd(_send_fd) { if (recv_fd < 0) throw SockError("Wrong input file descriptor"); if (send_fd < 0) throw SockError("Wrong output file descriptor"); sock_ok = true; } SockWrapper::~SockWrapper() { EndSessionCleanup(); sock_ok = false; b_log[W_SOCK] << "Closing sockets"; close(recv_fd); if (send_fd != recv_fd) close(send_fd); } SockWrapper* SockWrapper::Builder(ConfigSection* _config, int _recv_fd, int _send_fd) { string sec_mode = _config->GetItem("security")->String(); if (sec_mode.compare("none") == 0) return new SockWrapperPlain(_config, _recv_fd, _send_fd); #ifdef HAVE_TLS else if (sec_mode.compare("tls") == 0) return new SockWrapperTLS(_config, _recv_fd, _send_fd); else if (sec_mode.compare("starttls") == 0) throw SockError("Security mode not yet implemented"); else if (sec_mode.compare("starttls-mandatory") == 0) throw SockError("Security mode not yet implemented"); #endif throw SockError("unknown security mode"); } string SockWrapper::GetClientHostname() { struct sockaddr_storage sock; socklen_t socklen = sizeof(sock); b_log[W_SOCK] << "Fetching client hostname"; /* Get the client's hostname. */ string clienthost = "localhost.localdomain"; if(getpeername(recv_fd, (struct sockaddr*) &sock, &socklen) == 0) { char buf[NI_MAXHOST+1]; if(getnameinfo((struct sockaddr *)&sock, socklen, buf, NI_MAXHOST, NULL, 0, 0) == 0) clienthost = buf; } return clienthost; } string SockWrapper::GetServerHostname() { struct sockaddr_storage sock; socklen_t socklen = sizeof(sock); b_log[W_SOCK] << "Fetching server hostname"; /* Get the server's hostname. */ string serverhost = "localhost.localdomain"; if(getsockname(recv_fd, (struct sockaddr*) &sock, &socklen) == 0) { char buf[NI_MAXHOST+1]; if(getnameinfo((struct sockaddr *) &sock, socklen, buf, NI_MAXHOST, NULL, 0, 0 ) == 0) serverhost = buf; } return serverhost; } int SockWrapper::AttachCallback(PurpleInputCondition cond, _CallBack* cb) { int id = glib_input_add(recv_fd, cond, g_callback_input, cb); if (id > 0) callback_ids.push_back(id); return id; } void SockWrapper::EndSessionCleanup() { b_log[W_SOCK] << "Removing callbacks"; for(vector::iterator id = callback_ids.begin(); id != callback_ids.end(); ++id) g_source_remove(*id); } string SockWrapper::GetClientUsername() { b_log[W_INFO] << "Client Username not found"; return ""; } }; minbif-1.0.5+git20120508/src/sockwrap/sock.h0000644000175000017500000000171111754151773016231 0ustar sebseb#include #include #ifndef _WIN32 #include #include #include #include #include #define sock_make_nonblocking(fd) fcntl(fd, F_SETFL, O_NONBLOCK) #define sock_make_blocking(fd) fcntl(fd, F_SETFL, 0) #define sockerr_again() (errno == EINPROGRESS || errno == EINTR) #ifndef EVENTS_LIBEVENT #define closesocket(a) close(a) #endif #else # include # include # if !defined(BITLBEE_CORE) && defined(_MSC_VER) # pragma comment(lib,"bitlbee.lib") # endif # include # define sock_make_nonblocking(fd) { int non_block = 1; ioctlsocket(fd, FIONBIO, &non_block); } # define sock_make_blocking(fd) { int non_block = 0; ioctlsocket(fd, FIONBIO, &non_block); } # define sockerr_again() (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK) # define ETIMEDOUT WSAETIMEDOUT # define sleep(a) Sleep(a*1000) #endif minbif-1.0.5+git20120508/src/sockwrap/sockwrap.h0000644000175000017500000000355611754151773017134 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include "core/log.h" #include "core/config.h" #include "core/callback.h" #if defined(__FreeBSD__) || defined(__FreeBSD) || defined(__OpenBSD__) #include #include #endif #ifndef PF_SOCKWRAP_H #define PF_SOCKWRAP_H namespace sock { using std::string; using std::vector; LOGEXCEPTION2(SockError, LogException, W_SOCK); class SockWrapper { ConfigSection* config; vector callback_ids; public: static SockWrapper* Builder(ConfigSection* _config, int _recv_fd, int _send_fd); SockWrapper(ConfigSection* _config, int _recv_fd, int _send_fd); virtual ~SockWrapper(); ConfigSection* getConfig() const { return config; } virtual string Read() = 0; virtual void Write(string s) = 0; virtual string GetClientHostname(); virtual string GetServerHostname(); virtual int AttachCallback(PurpleInputCondition cond, _CallBack* cb); virtual string GetClientUsername(); protected: int recv_fd, send_fd; bool sock_ok; virtual void EndSessionCleanup(); }; }; #endif /* PF_SOCKWRAP_H */ minbif-1.0.5+git20120508/src/sockwrap/sockwrap_plain.cpp0000644000175000017500000000353111754151773020643 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sockwrap_plain.h" #include "sock.h" #include #include namespace sock { SockWrapperPlain::SockWrapperPlain(ConfigSection* _config, int _recv_fd, int _send_fd) : SockWrapper(_config, _recv_fd, _send_fd) { b_log[W_SOCK] << "Plain connection initialized"; } SockWrapperPlain::~SockWrapperPlain() { } string SockWrapperPlain::Read() { static char buf[1024]; string sbuf; ssize_t r; if (!sock_ok) return ""; if ((r = read(recv_fd, buf, sizeof buf - 1)) <= 0) { if (r == 0) throw SockError("Connection reset by peer..."); else if(!sockerr_again()) throw SockError(string("Read error: ") + strerror(errno)); else sbuf = ""; } else { buf[r] = 0; sbuf = buf; } return sbuf; } void SockWrapperPlain::Write(string s) { ssize_t r; if (!sock_ok) return; if ((r = write(send_fd, s.c_str(), s.size())) <= 0) { if (r == 0) { sock_ok = false; throw SockError("Connection reset by peer..."); } else if(!sockerr_again()) { sock_ok = false; throw SockError(string("Read error: ") + strerror(errno)); } } } }; minbif-1.0.5+git20120508/src/CMakeLists.txt0000644000175000017500000000262211754151773016032 0ustar sebsebSET(BIN_NAME minbif) IF(PAM_FOUND) SET(MINBIF_EXTRA_FILES_PAM "im/auth_pam.cpp") ENDIF(PAM_FOUND) IF(GNUTLS_FOUND) SET(MINBIF_EXTRA_FILES_TLS "sockwrap/sockwrap_tls.cpp") ENDIF(GNUTLS_FOUND) ADD_EXECUTABLE(${BIN_NAME} core/minbif.cpp core/sighandler.cpp core/util.cpp core/log.cpp core/mutex.cpp core/callback.cpp core/config.cpp core/caca_image.cpp sockwrap/sockwrap.cpp sockwrap/sockwrap_plain.cpp ${MINBIF_EXTRA_FILES_TLS} server_poll/poll.cpp server_poll/inetd.cpp server_poll/daemon_fork.cpp im/im.cpp im/auth.cpp im/auth_local.cpp im/auth_connection.cpp ${MINBIF_EXTRA_FILES_PAM} im/plugin.cpp im/protocol.cpp im/account.cpp im/roomlist.cpp im/buddy.cpp im/conversation.cpp im/request.cpp im/ft.cpp im/media.cpp im/purple.cpp irc/settings.cpp irc/irc.cpp irc/cmds.cpp irc/cmds_accounts.cpp irc/cmds_channels.cpp irc/dcc.cpp irc/message.cpp irc/server.cpp irc/nick.cpp irc/user.cpp irc/buddy_icon.cpp irc/conv_entity.cpp irc/buddy.cpp irc/unknown_buddy.cpp irc/chat_buddy.cpp irc/channel.cpp irc/status_channel.cpp irc/conversation_channel.cpp ) TARGET_LINK_LIBRARIES(${BIN_NAME} "-lpthread -lstdc++" ${PURPLE_LIBRARIES} ${GTHREAD_LIBRARIES} ${CACA_LIBRARIES} ${IMLIB_LIBRARIES} ${GSTREAMER_LIBRARIES} ${FARSIGHT_LIBRARIES} ${PAM_LIBRARIES} ${GNUTLS_LIBRARIES}) INSTALL(TARGETS ${BIN_NAME} DESTINATION bin) minbif-1.0.5+git20120508/src/irc/0000755000175000017500000000000011754151773014045 5ustar sebsebminbif-1.0.5+git20120508/src/irc/server.cpp0000644000175000017500000000276311754151773016067 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "server.h" #include "nick.h" namespace irc { Server::Server(string name, string _info) : Entity(name), info(_info) {} void Server::addNick(Nick* n) { users.push_back(n); } void Server::removeNick(Nick* n) { for(vector::iterator it = users.begin(); it != users.end(); ++it) if (*it == n) { users.erase(it); return; } } unsigned Server::countNicks() const { return users.size(); } unsigned Server::countOnlineNicks() const { unsigned i = 0; for(vector::const_iterator it = users.begin(); it != users.end(); ++it) if((*it)->isOnline()) i++; return i; } RemoteServer::RemoteServer(IRC* _irc, im::Account _account) : Server(_account.getServername(), _account.getProtocol().getName()), account(_account), irc(_irc) { } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/buddy_icon.h0000644000175000017500000000244211754151773016337 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_BUDDY_ICON_H #define IRC_BUDDY_ICON_H #include "irc/nick.h" namespace im { class IM; }; namespace irc { /** This class represents a buddy on IRC */ class BuddyIcon : public Nick { im::IM* im; bool receivedIcon(void* data); public: /** Build buddy object * * @param im IM instance * @param server up-server */ BuddyIcon(im::IM* im, Server* server); ~BuddyIcon(); /** Implementation of the message routing to this buddy */ virtual void send(Message m); }; }; /* namespace irc */ #endif /* IRC_BUDDY_ICON_H */ minbif-1.0.5+git20120508/src/irc/buddy.h0000644000175000017500000000451411754151773015331 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_BUDDY_H #define IRC_BUDDY_H #include "irc/conv_entity.h" #include "im/buddy.h" namespace irc { /** This class represents a buddy on IRC */ class Buddy : public ConvNick { im::Buddy im_buddy; bool public_msgs; time_t public_msgs_last; static const int PUBLIC_MSGS_TIMEOUT = 3600; bool process_dcc_get(const string& text); bool received_file(void* data); void check_conv(void); public: /** Build buddy object * * @param server up-server * @param buddy IM buddy object. */ Buddy(Server* server, im::Buddy buddy); ~Buddy(); /** Implementation of the message routing to this buddy */ virtual void send(Message m); /** Buddy sends a message to someone. */ virtual void sendMessage(Nick* to, const string& text, bool action = false); /** Get status. * @param away if false, do not return anything if the buddy is away. */ virtual string getStatusMessage(bool away = false) const; /** Get buddy's away message. */ virtual string getAwayMessage() const; /** Check if user is away or not. */ virtual bool isAway() const; /** Check if buddy is online or not. */ virtual bool isOnline() const; im::Buddy getBuddy() const { return im_buddy; } virtual int sendCommand(const string& cmd); /** Get icon in an coloured ASCII-art form. */ virtual CacaImage getIcon() const; virtual string getIconPath() const; /** Get buddy's real name. */ virtual string getRealName() const; virtual bool retrieveInfo() const; virtual void setConversation(const im::Conversation& c); }; }; /* namespace irc */ #endif /* IRC_BUDDY_H */ minbif-1.0.5+git20120508/src/irc/conversation_channel.cpp0000644000175000017500000001251211754151773020754 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "conversation_channel.h" #include "chat_buddy.h" #include "nick.h" #include "user.h" #include "irc.h" #include "core/log.h" namespace irc { ConversationChannel::ConversationChannel(IRC* irc, const im::Conversation& conv) : Channel(irc, conv.getChanName()), ConvEntity(conv), upserver(NULL) { upserver = dynamic_cast(irc->getServer(conv.getAccount().getServername())); } ConversationChannel::~ConversationChannel() { map::iterator it; for(it = cbuddies.begin(); it != cbuddies.end(); ++it) { irc::ChatBuddy* cb = dynamic_cast(it->second->getNick()); if(cb) { /* Call the Channel function to not erase chatbuddy from cbuddies, * because I iterate on. * I know that Channel destructor will also remove chanuser from list, * but it may also call some Nick methods, as the object is deleted * by IRC::removeNick()... */ Channel::delUser(cb); cb->removeChanUser(it->second); irc->removeNick(cb->getNickname()); } } } void ConversationChannel::showBanList(Nick* to) { to->send(Message(RPL_ENDOFBANLIST).setSender(irc) .setReceiver(to) .addArg(getName()) .addArg("End of Channel Ban List")); } void ConversationChannel::processBan(Nick* from, string pattern, bool add) { } void ConversationChannel::addBuddy(im::ChatBuddy cbuddy, int status) { ChanUser* cul; if(cbuddy.isMe()) cul = irc->getUser()->join(this, status); else { map::iterator it = cbuddies.find(cbuddy); if(it == cbuddies.end()) { ChatBuddy* n = new ChatBuddy(upserver, cbuddy); while(irc->getNick(n->getNickname())) n->setNickname(n->getNickname() + "_"); irc->addNick(n); cul = n->join(this, status); } else cul = it->second; } cbuddies[cbuddy] = cul; } void ConversationChannel::updateBuddy(im::ChatBuddy cbuddy) { ChanUser* chanuser = getChanUser(cbuddy); int mlast = chanuser->getStatus(); int mnew = cbuddy.getChanStatus(); int add = 0, del = 0; for(unsigned i = 0; i < (sizeof i) * 8; ++i) { if(!(mlast & (1 << i)) && mnew & (1 << i)) add |= 1 << i; if(mlast & (1 << i) && !(mnew & (1 << i))) del |= 1 << i; } if(add) this->setMode(irc, add, chanuser); if(del) this->delMode(irc, del, chanuser); } void ConversationChannel::renameBuddy(ChanUser* chanuser, im::ChatBuddy cbuddy) { ChatBuddy* nick = dynamic_cast(chanuser->getNick()); string new_nick = cbuddy.getName(); while(irc->getNick(new_nick)) new_nick += "_"; irc->getUser()->send(irc::Message(MSG_NICK).setSender(nick) .addArg(new_nick)); irc->renameNick(nick, new_nick); cbuddies.erase(nick->getChatBuddy()); nick->setChatBuddy(cbuddy); cbuddies[cbuddy] = chanuser; } void ConversationChannel::delUser(Nick* nick, Message message) { map::iterator it; for(it = cbuddies.begin(); it != cbuddies.end() && it->second->getNick() != nick; ++it) ; if(it != cbuddies.end()) cbuddies.erase(it); Channel::delUser(nick, message); irc::ChatBuddy* cb = dynamic_cast(nick); if(cb) irc->removeNick(cb->getNickname()); else if(nick == irc->getUser()) getConversation().leave(); } ChanUser* ConversationChannel::getChanUser(string nick) const { map::const_iterator it; nick = strlower(nick); for(it = cbuddies.begin(); it != cbuddies.end(); ++it) if(strlower(it->first.getName()) == nick) return it->second; return NULL; } ChanUser* ConversationChannel::getChanUser(const im::ChatBuddy& cb) const { map::const_iterator it = cbuddies.find(cb); if(it != cbuddies.end()) return it->second; else return NULL; } void ConversationChannel::broadcast(Message m, Nick* butone) { if(m.getCommand() == MSG_PRIVMSG && m.getSender() == irc->getUser()) { enqueueMessage(m.getArg(0), irc->getIM()->getSendDelay()); } else Channel::broadcast(m, butone); } string ConversationChannel::getTopic() const { return getConversation().getChanTopic(); } bool ConversationChannel::invite(Nick* nick, const string& buddy, const string& message) { getConversation().invite(buddy, message); return true; } bool ConversationChannel::kick(ChanUser* from, ChanUser* victim, const string& message) { /* TODO implement it */ return false; } bool ConversationChannel::setTopic(Entity* from, const string& topic) { if(irc->getUser() == from) return getConversation().setTopic(topic); else return Channel::setTopic(from, topic); } int ConversationChannel::sendCommand(const string& cmd) { return getConversation().sendCommand(cmd); } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/user.cpp0000644000175000017500000000303111754151773015524 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "user.h" #include "server.h" namespace irc { User::User(sock::SockWrapper* _sockw, Server* server, string nickname, string identname, string hostname, string realname) : Nick(server, nickname, identname, hostname, realname), sockw(_sockw) { } User::~User() { } void User::send(Message msg) { if (sockw) sockw->Write(msg.format()); } void User::setLastReadNow() { last_read = time(NULL); } void User::m_mode(Nick* user, Message m) { if(m.countArgs() == 0) { user->send(Message(RPL_UMODEIS).setSender(getServer()) .setReceiver(user) .addArg(getModes())); return; } /* TODO set personal modes. */ } string User::getModes() const { string modes = "+"; if(hasFlag(OPER)) modes += "o"; return modes; } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/status_channel.cpp0000644000175000017500000001256411754151773017574 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "irc.h" #include "status_channel.h" #include "nick.h" #include "buddy.h" #include "im/im.h" #include "im/account.h" #include "core/util.h" #include "core/log.h" namespace irc { StatusChannel::StatusChannel(IRC* irc, string name) : Channel(irc, name) {} void StatusChannel::addAccount(const im::Account& account) { accounts.push_back(account); } void StatusChannel::removeAccount(const im::Account& account) { vector::iterator it = std::find(accounts.begin(), accounts.end(), account); if(it != accounts.end()) accounts.erase(it); } string StatusChannel::getMaskFromName(const string& name, const im::Account& acc) const { string ban = "*!" + name; if(ban.find('@') == string::npos) ban += "@" + acc.getID(); else ban += ":" + acc.getID(); return ban; } void StatusChannel::showBanList(Nick* to) { FOREACH(vector, accounts, account) { vector deny_list = account->getDenyList(); FOREACH(vector, deny_list, str) { to->send(Message(RPL_BANLIST).setSender(irc) .setReceiver(to) .addArg(getName()) .addArg(getMaskFromName(*str, *account)) .addArg(account->getServername()) .addArg("0")); } } to->send(Message(RPL_ENDOFBANLIST).setSender(irc) .setReceiver(to) .addArg(getName()) .addArg("End of Channel Ban List")); } void StatusChannel::processBan(Nick* from, string pattern, bool add) { bool (im::Account::*func) (const string&) const; string mode; if (add) { mode = "+b"; func = &im::Account::deny; } else { mode = "-b"; func = &im::Account::allow; } vector results = irc->matchNick(pattern); if(!results.empty()) { for(vector::iterator n = results.begin(); n != results.end(); ++n) { Buddy* nick = dynamic_cast(*n); if (!nick) continue; im::Buddy buddy = nick->getBuddy(); if ((buddy.getAccount().*func)(buddy.getName())) broadcast(Message(MSG_MODE).setSender(from) .setReceiver(this) .addArg(mode) .addArg(getMaskFromName(nick->getBuddy().getName(), nick->getBuddy().getAccount()))); } } else { im::Account acc; string accid = pattern; pattern = stringtok(accid, ":"); if (accid.empty()) { accid = pattern; pattern = stringtok(accid, "@"); } vector::iterator it; for(it = accounts.begin(); it != accounts.end() && it->getID() != accid; ++it) ; if (it != accounts.end()) acc = *it; else if(accounts.size() == 1) acc = accounts.front(); else { from->send(Message(ERR_NOSUCHNICK).setSender(irc) .setReceiver(from) .addArg(accid.empty() ? pattern : accid) .addArg(accid.empty() ? "Please sufix mask with ':accountID'" : "No such account")); return; } if (pattern.find('!') != string::npos) stringtok(pattern, "!"); if ((acc.*func)(pattern)) broadcast(Message(MSG_MODE).setSender(from) .setReceiver(this) .addArg(mode) .addArg(getMaskFromName(pattern, acc))); } } bool StatusChannel::invite(Nick* from, const string& nickname, const string& message) { string acc = nickname; string username = stringtok(acc, ":"); im::Account account; if(acc.empty()) account = irc->getIM()->getAccountFromChannel(getName()); else account = irc->getIM()->getAccount(acc); if(!account.isValid()) { from->send(Message(ERR_NOSUCHCHANNEL).setSender(irc) .setReceiver(from) .addArg(nickname) .addArg("No such channel")); return false; } account.addBuddy(username, "minbif"); return true; } bool StatusChannel::kick(ChanUser* from, ChanUser* victim, const string& message) { Buddy* buddy = dynamic_cast(victim->getNick()); if(!buddy) { from->getNick()->send(Message(ERR_CHANOPRIVSNEEDED).setSender(irc) .setReceiver(from) .addArg(getName()) .addArg("Permission denied: you can only kick a buddy")); return false; } RemoteServer* rt = dynamic_cast(buddy->getServer()); if(!rt) { irc->notice(from->getNick(), victim->getName() + " is not on a remote server"); return false; } string reason = "Removed from buddy list"; if(message.empty() == false) reason += ": " + message; buddy->kicked(this, from, reason); rt->getAccount().removeBuddy(buddy->getBuddy()); return true; } bool StatusChannel::setTopic(Entity* from, const string& message) { if (!Channel::setTopic(from, message)) return false; for(vector::iterator acc = accounts.begin(); acc != accounts.end(); ++acc) acc->setStatus(PURPLE_STATUS_AVAILABLE, message); return true; } }; /* ns irc */ minbif-1.0.5+git20120508/src/irc/conv_entity.cpp0000644000175000017500000000410111754151773017106 0ustar sebseb/* * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "core/callback.h" #include "irc/nick.h" #include "irc/conv_entity.h" namespace irc { ConvEntity::ConvEntity(im::Conversation _conv) : conv(_conv) { } void ConvEntity::enqueueMessage(const string& text, int delay) { if (!delay) { conv.sendMessage(text); return; } enqueued_messages.push_back(text); if (enqueued_messages.size() == 1) g_timeout_add(delay, g_callback_delete, new CallBack(this, &ConvEntity::flush_messages, NULL)); } bool ConvEntity::flush_messages(void*) { string buf; for (vector::iterator s = enqueued_messages.begin(); s != enqueued_messages.end(); s = enqueued_messages.erase(s)) { if((*s)[0] == '\001') { if (!buf.empty()) { conv.sendMessage(buf); buf.clear(); } conv.sendMessage(*s); } else { if (!buf.empty()) buf += '\n'; buf += *s; } } if (!buf.empty()) conv.sendMessage(buf); return false; } ConvNick::ConvNick(Server* server, im::Conversation conv, string nickname, string identname, string hostname, string realname) : Nick(server, nickname, identname, hostname, realname), ConvEntity(conv) {} void ConvNick::sendMessage(Nick* to, const string& t, bool action) { string line = t; if(action) line = "\001ACTION " + line + "\001"; to->send(irc::Message(MSG_PRIVMSG).setSender(this) .setReceiver(to) .addArg(line)); } } /* ns irc */ minbif-1.0.5+git20120508/src/irc/irc.cpp0000644000175000017500000003541611754151773015337 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon, Marc Dequènes (Duck) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include "core/log.h" #include "core/util.h" #include "core/version.h" #include "server_poll/poll.h" #include "irc/irc.h" #include "irc/buddy.h" #include "irc/dcc.h" #include "irc/user.h" #include "irc/channel.h" namespace irc { IRC::command_t IRC::commands[] = { { MSG_NICK, &IRC::m_nick, 0, 0, 0 }, { MSG_USER, &IRC::m_user, 4, 0, 0 }, { MSG_PASS, &IRC::m_pass, 1, 0, 0 }, { MSG_QUIT, &IRC::m_quit, 0, 0, 0 }, { MSG_CMD, &IRC::m_cmd, 2, 0, Nick::REGISTERED }, { MSG_PRIVMSG, &IRC::m_privmsg, 2, 0, Nick::REGISTERED }, { MSG_PING, &IRC::m_ping, 0, 0, Nick::REGISTERED }, { MSG_PONG, &IRC::m_pong, 1, 0, Nick::REGISTERED }, { MSG_VERSION, &IRC::m_version, 0, 0, Nick::REGISTERED }, { MSG_INFO, &IRC::m_info, 0, 0, Nick::REGISTERED }, { MSG_WHO, &IRC::m_who, 0, 0, Nick::REGISTERED }, { MSG_WHOIS, &IRC::m_whois, 1, 0, Nick::REGISTERED }, { MSG_WHOWAS, &IRC::m_whowas, 1, 0, Nick::REGISTERED }, { MSG_STATS, &IRC::m_stats, 0, 0, Nick::REGISTERED }, { MSG_CONNECT, &IRC::m_connect, 1, 0, Nick::REGISTERED }, { MSG_SCONNECT,&IRC::m_connect, 1, 0, Nick::REGISTERED }, { MSG_SQUIT, &IRC::m_squit, 1, 0, Nick::REGISTERED }, { MSG_MAP, &IRC::m_map, 0, 0, Nick::REGISTERED }, { MSG_ADMIN, &IRC::m_admin, 0, 0, Nick::REGISTERED }, { MSG_JOIN, &IRC::m_join, 1, 0, Nick::REGISTERED }, { MSG_PART, &IRC::m_part, 1, 0, Nick::REGISTERED }, { MSG_NAMES, &IRC::m_names, 1, 0, Nick::REGISTERED }, { MSG_TOPIC, &IRC::m_topic, 1, 0, Nick::REGISTERED }, { MSG_LIST, &IRC::m_list, 0, 0, Nick::REGISTERED }, { MSG_MODE, &IRC::m_mode, 1, 0, Nick::REGISTERED }, { MSG_ISON, &IRC::m_ison, 1, 0, Nick::REGISTERED }, { MSG_INVITE, &IRC::m_invite, 2, 0, Nick::REGISTERED }, { MSG_KICK, &IRC::m_kick, 2, 0, Nick::REGISTERED }, { MSG_KILL, &IRC::m_kill, 1, 0, Nick::REGISTERED }, { MSG_SVSNICK, &IRC::m_svsnick, 2, 0, Nick::REGISTERED }, { MSG_AWAY, &IRC::m_away, 0, 0, Nick::REGISTERED }, { MSG_MOTD, &IRC::m_motd, 0, 0, Nick::REGISTERED }, { MSG_OPER, &IRC::m_oper, 2, 0, Nick::REGISTERED }, { MSG_WALLOPS, &IRC::m_wallops, 1, 0, Nick::OPER }, { MSG_REHASH, &IRC::m_rehash, 0, 0, Nick::OPER }, { MSG_DIE, &IRC::m_die, 1, 0, Nick::OPER }, { NULL, NULL, 0, 0, 0 }, }; IRC::IRC(ServerPoll* _poll, sock::SockWrapper* _sockw, string _hostname, unsigned _ping_freq) : Server("localhost.localdomain", MINBIF_VERSION), poll(_poll), sockw(_sockw), read_cb(NULL), ping_id(-1), ping_freq(_ping_freq), uptime(time(NULL)), ping_cb(NULL), user(NULL), im(NULL), im_auth(NULL) { /* Get my own hostname (if not given in arguments) */ if(_hostname.empty() || _hostname == " ") setName(sockw->GetServerHostname()); else if(_hostname.find(" ") != string::npos) { /* An hostname can't contain any space. */ b_log[W_ERR] << "'" << _hostname << "' is not a valid server hostname"; throw sock::SockError("Wrong server hostname"); } else setName(_hostname); /* create a callback on the sock. */ read_cb = new CallBack(this, &IRC::readIO); sockw->AttachCallback(PURPLE_INPUT_READ, read_cb); /* Create main objects and root joins command channel. */ user = new User(sockw, this, "*", "", sockw->GetClientHostname()); addNick(user); /* Ping callback */ if(ping_freq > 0) { ping_cb = new CallBack(this, &IRC::ping); ping_id = g_timeout_add((int)ping_freq * 1000, g_callback, ping_cb); } rehash(false); user->send(Message(MSG_NOTICE).setSender(this).setReceiver("AUTH").addArg("Minbif-IRCd initialized, please go on")); } IRC::~IRC() { delete im; if (im_auth) delete im_auth; if(ping_id >= 0) g_source_remove(ping_id); delete ping_cb; if(sockw) delete sockw; delete read_cb; cleanUpNicks(); cleanUpServers(); cleanUpChannels(); cleanUpDCC(); } DCC* IRC::createDCCSend(const im::FileTransfert& ft, Nick* n) { DCC* dcc = new DCCSend(ft, n, user); dccs.push_back(dcc); return dcc; } DCC* IRC::createDCCGet(Nick* from, string filename, uint32_t addr, uint16_t port, ssize_t size, _CallBack* callback) { DCC* dcc = new DCCGet(from, filename, addr, port, size, callback); dccs.push_back(dcc); return dcc; } void IRC::updateDCC(const im::FileTransfert& ft, bool destroy) { for(vector::iterator it = dccs.begin(); it != dccs.end();) { /* Purge */ if((*it)->isFinished()) { delete *it; it = dccs.erase(it); } else { if((*it)->getFileTransfert() == ft) (*it)->updated(destroy); ++it; } } } void IRC::cleanUpDCC() { for(vector::iterator it = dccs.begin(); it != dccs.end(); ++it) delete *it; dccs.clear(); } void IRC::addChannel(Channel* chan) { if(channels.find(chan->getName()) != channels.end()) b_log[W_DESYNCH] << "/!\\ Channel " << chan->getName() << " already exists!"; channels[chan->getName()] = chan; } Channel* IRC::getChannel(string channame) const { map::const_iterator it = channels.find(channame); if(it == channels.end()) return 0; return it->second; } void IRC::removeChannel(string channame) { map::iterator it = channels.find(channame); if(it != channels.end()) { delete it->second; channels.erase(it); } } void IRC::cleanUpChannels() { map::iterator it; for(it = channels.begin(); it != channels.end(); ++it) delete it->second; channels.clear(); } void IRC::addNick(Nick* nick) { if(users.find(nick->getNickname()) != users.end()) b_log[W_DESYNCH] << "/!\\ User " << nick->getNickname() << " already exists!"; users[nick->getNickname()] = nick; nick->getServer()->addNick(nick); } void IRC::renameNick(Nick* nick, string newnick) { users.erase(nick->getNickname()); nick->setNickname(newnick); addNick(nick); } Nick* IRC::getNick(string nickname, bool case_sensitive) const { map::const_iterator it; if(!case_sensitive) nickname = strlower(nickname); for(it = users.begin(); it != users.end() && (case_sensitive ? it->first : strlower(it->first)) != nickname; ++it) ; if(it == users.end()) return 0; return it->second; } Buddy* IRC::getNick(const im::Buddy& buddy) const { map::const_iterator it; Buddy* nb = NULL; for(it = users.begin(); it != users.end() && (!(nb = dynamic_cast(it->second)) || nb->getBuddy() != buddy); ++it) ; if(it == users.end()) return NULL; else return nb; } ConvNick* IRC::getNick(const im::Conversation& conv) const { map::const_iterator it; ConvNick* n = NULL; for(it = users.begin(); it != users.end() && (!(n = dynamic_cast(it->second)) || n->getConversation() != conv); ++it) ; if(it == users.end()) return NULL; else return n; } vector IRC::matchNick(string pattern) const { map::const_iterator it; vector result; if (pattern.find('!') == string::npos) { if (pattern.find('@') != string::npos) pattern = "*!" + pattern; else if (pattern.find(':') != string::npos) pattern = "*!*@" + pattern; else pattern = pattern + "!*@*"; } else if (pattern.find('@') == string::npos) pattern += "@*"; for (it = users.begin(); it != users.end(); ++it) { string longname = it->second->getLongName(); if (!fnmatch(pattern.c_str(), longname.c_str(), FNM_NOESCAPE|FNM_CASEFOLD)) result.push_back(it->second); } return result; } void IRC::removeNick(string nickname) { map::iterator it = users.find(nickname); if(it != users.end()) { for(vector::iterator dcc = dccs.begin(); dcc != dccs.end();) if((*dcc)->isFinished()) { delete *dcc; dcc = dccs.erase(dcc); } else { if((*dcc)->getPeer() == it->second) (*dcc)->setPeer(NULL); ++dcc; } it->second->getServer()->removeNick(it->second); delete it->second; users.erase(it); } } void IRC::cleanUpNicks() { map::iterator it; for(it = users.begin(); it != users.end(); ++it) { it->second->getServer()->removeNick(it->second); delete it->second; } users.clear(); } void IRC::addServer(Server* server) { servers[server->getName()] = server; } Server* IRC::getServer(string servername) const { map::const_iterator it = servers.find(servername); if(it == servers.end()) return 0; return it->second; } void IRC::removeServer(string servername) { map::iterator it = servers.find(servername); if(it != servers.end()) { /* Cleanup server's users */ for(map::iterator nt = users.begin(); nt != users.end();) if(nt->second->getServer() == it->second) { delete nt->second; users.erase(nt); nt = users.begin(); } else ++nt; delete it->second; servers.erase(it); } } void IRC::cleanUpServers() { map::iterator it; for(it = servers.begin(); it != servers.end(); ++it) delete it->second; servers.clear(); } void IRC::rehash(bool verbose) { setMotd(conf.GetSection("path")->GetItem("motd")->String()); if(verbose) b_log[W_INFO|W_SNO] << "Server configuration rehashed."; } void IRC::setMotd(const string& path) { std::ifstream fp(path.c_str()); if(!fp) { b_log[W_WARNING] << "Unable to read MOTD"; return; } char buf[512]; motd.clear(); while(fp) { fp.getline(buf, 511); motd.push_back(buf); } fp.close(); } void IRC::quit(string reason) { user->send(Message(MSG_ERROR).addArg("Closing Link: " + reason)); user->close(); delete sockw; sockw = NULL; poll->kill(this); } void IRC::sendWelcome() { if(user->hasFlag(Nick::REGISTERED) || user->getNickname() == "*" || user->getIdentname().empty()) return; try { im_auth = im::Auth::validate(this, user->getNickname(), user->getPassword()); if (!im_auth) { if (im::IM::exists(user->getNickname())) { quit("Incorrect credentials"); return; } /* New User */ string global_passwd = conf.GetSection("irc")->GetItem("password")->String(); if(global_passwd != " " && user->getPassword() != global_passwd) { quit("This server is protected by a global private password. Ask administrator."); return; } im_auth = im::Auth::generate(this, user->getNickname(), user->getPassword()); if (!im_auth) quit("Creation of new account failed"); } im = im_auth->getIM(); user->setFlag(Nick::REGISTERED); poll->ipc_send(Message(MSG_USER).addArg(getUser()->getNickname())); // http://irchelp.org/irchelp/rfc/rfc2812.txt 5.1 - // "The server sends Replies 001 to 004 to a user upon successful registration." user->send(Message(RPL_WELCOME).setSender(this).setReceiver(user).addArg("Welcome to the Minbif IRC gateway, " + user->getNickname() + "!")); user->send(Message(RPL_YOURHOST).setSender(this).setReceiver(user).addArg("Your host is " + getServerName() + ", running " MINBIF_VERSION)); user->send(Message(RPL_CREATED).setSender(this).setReceiver(user).addArg("This server was created " __DATE__ " " __TIME__)); user->send(Message(RPL_MYINFO).setSender(this).setReceiver(user).addArg(getServerName() + " " + MINBIF_VERSION + " " + Nick::UMODES + " " + Channel::CHMODES)); user->send(Message(RPL_ISUPPORT).setSender(this).setReceiver(user).addArg("CMDS=MAP") /* TODO it doesn't compile because g++ is crappy. * .addArg("NICKLEN=" + t2s(Nick::MAX_LENGTH)) */ .addArg("CHANTYPE=#&") .addArg("PREFIX=(qohv)~@%+") .addArg("STATUSMSG=~@%+") .addArg("are supported by this server")); m_motd(Message()); im->restore(); if (im->isAway()) { user->setAwayMessage("Away"); user->send(Message(RPL_NOWAWAY).setSender(this) .setReceiver(user) .addArg("You have been marked as being away")); } } catch(im::IMError& e) { quit("Unable to initialize IM: " + e.Reason()); } } bool IRC::ping(void*) { if(user->getLastRead() + ping_freq > time(NULL)) return true; if(!user->hasFlag(Nick::REGISTERED) || user->hasFlag(Nick::PING)) { quit("Ping timeout"); return false; } else { user->setFlag(Nick::PING); user->send(Message(MSG_PING).addArg(getServerName())); return true; } } void IRC::notice(Nick* nick, string msg) { string tmp; while((tmp = stringtok(msg, "\n\r")).empty() == false) nick->send(Message(MSG_NOTICE).setSender(this) .setReceiver(user) .addArg(tmp)); } void IRC::privmsg(Nick* nick, string msg) { string tmp; while((tmp = stringtok(msg, "\n\r")).empty() == false) nick->send(Message(MSG_PRIVMSG).setSender(this) .setReceiver(user) .addArg(tmp)); } bool IRC::readIO(void*) { try { string sbuf, line; sbuf = sockw->Read(); while((line = stringtok(sbuf, "\r\n")).empty() == false) { Message m = Message::parse(line); b_log[W_PARSE] << "<< " << line; size_t i; for(i = 0; commands[i].cmd != NULL && strcmp(commands[i].cmd, m.getCommand().c_str()); ++i) ; user->setLastReadNow(); if(commands[i].cmd == NULL) user->send(Message(ERR_UNKNOWNCOMMAND).setSender(this) .setReceiver(user) .addArg(m.getCommand()) .addArg("Unknown command")); else if(m.countArgs() < commands[i].minargs) user->send(Message(ERR_NEEDMOREPARAMS).setSender(this) .setReceiver(user) .addArg(m.getCommand()) .addArg("Not enough parameters")); else if(commands[i].flags && !user->hasFlag(commands[i].flags)) { if(!user->hasFlag(Nick::REGISTERED)) user->send(Message(ERR_NOTREGISTERED).setSender(this) .setReceiver(user) .addArg("Register first")); else user->send(Message(ERR_NOPRIVILEGES).setSender(this) .setReceiver(user) .addArg("Permission Denied: Insufficient privileges")); } else { commands[i].count++; (this->*commands[i].func)(m); } } } catch (sock::SockError &e) { quit(e.Reason()); } return true; } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/dcc.h0000644000175000017500000001067011754151773014753 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_DCC_H #define IRC_DCC_H #include #include #include #include #include "im/ft.h" class _CallBack; namespace irc { using std::string; class Nick; /* Exceptions */ class DCCListenError : public std::exception {}; class DCCGetError : public std::exception {}; class DCC { public: virtual ~DCC() {} virtual im::FileTransfert getFileTransfert() const = 0; virtual void updated(bool destroy) = 0; virtual bool isFinished() const = 0; virtual Nick* getPeer() const = 0; virtual void setPeer(Nick* n) = 0; }; class DCCServer : public DCC { protected: static const time_t TIMEOUT = 5*60; string type; string filename; size_t total_size; time_t start_time; Nick* sender; Nick* receiver; PurpleNetworkListenData* listen_data; int watcher; int fd; unsigned port; bool finished; static void listen_cb(int sock, void* data); static void connected(gpointer data, int source, PurpleInputCondition cond); static void dcc_read_cb(gpointer data, int source, PurpleInputCondition cond); virtual void deinit(); virtual void dcc_read(int source) = 0; public: DCCServer(string type, string filename, size_t total_size, Nick* sender, Nick* receiver); ~DCCServer(); bool isFinished() const { return finished; } Nick* getPeer() const { return sender; } void setPeer(Nick* n) { sender = n; } }; /** The DCC class used to send a file to a IRC user. * * An instance of this class is created when a file transfert starts * on im->minbif. It creates a DCC server on a random port. * * When IRC user is connected on server, try to open the file that * libpurple is currently writting. If success, read 512 bytes and * send it to IRC user with DCC connection. * * Everytimes we receive an ACK from IRC user on DCC connection, or * when libpurple sends us a percentage update, retry to send data * to DCC user. * * When im->minbif transfert is finished, the minbif->irc transfert * isn't finished. So the 'ft' reference is removed, and this is the * read callback which ask to retry to send data to DCC user from * file. * * The file descriptor keeps open. */ class DCCSend : public DCCServer { im::FileTransfert ft; string local_filename; size_t bytes_sent; FILE* fp; guint rxlen; guchar* rxqueue; virtual void deinit(); virtual void dcc_read(int source); void dcc_send(); public: DCCSend(const im::FileTransfert& ft, Nick* sender, Nick* receiver); ~DCCSend(); im::FileTransfert getFileTransfert() const { return ft; } void updated(bool destroy); }; class DCCChat : public DCCServer { virtual void deinit(); virtual void dcc_read(int source); public: DCCChat(Nick* sender, Nick* receiver); ~DCCChat(); im::FileTransfert getFileTransfert() const { return im::FileTransfert(); } void updated(bool destroy); void dcc_send(string buf); }; class DCCGet : public DCC { Nick* from; string filename; _CallBack* callback; bool finished; int sock; int watcher; FILE* fp; ssize_t bytes_received; ssize_t total_size; void deinit(); static void dcc_read(gpointer data, int source, PurpleInputCondition cond); public: /** Get a file from a user, and call a method when it is finished. */ DCCGet(Nick* from, string filename, uint32_t addr, uint16_t port, ssize_t size, _CallBack* callback); ~DCCGet(); static bool parseDCCSEND(string line, string* filename, uint32_t* addr, uint16_t* port, ssize_t* size); virtual im::FileTransfert getFileTransfert() const { return im::FileTransfert(); } virtual void updated(bool destroy); virtual bool isFinished() const { return finished; } virtual Nick* getPeer() const { return from; } virtual void setPeer(Nick* n); }; }; #endif /* IRC_DCC_H */ minbif-1.0.5+git20120508/src/irc/buddy_icon.cpp0000644000175000017500000000416411754151773016675 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "irc/buddy_icon.h" #include "irc/irc.h" #include "irc/dcc.h" #include "im/im.h" #include "core/callback.h" #include "core/util.h" #include "core/log.h" namespace irc { BuddyIcon::BuddyIcon(im::IM* _im, Server* server) : Nick(server, "buddyicon", "buddyicon", server->getName(), "DCC Send me a .png to set your icon"), im(_im) { } BuddyIcon::~BuddyIcon() {} void BuddyIcon::send(Message m) { if(m.getCommand() == MSG_PRIVMSG && m.getReceiver() == this && m.countArgs() > 0) { string filename; uint32_t addr; uint16_t port; ssize_t size; if(DCCGet::parseDCCSEND(m.getArg(0), &filename, &addr, &port, &size)) { string path = im->getBuddyIconPath(); if(!check_write_file(path, filename)) { b_log[W_ERR] << "Unable to write into the buddy icon directory '" << path << "': " << strerror(errno); return; } try { IRC* irc = dynamic_cast(getServer()); filename = path + "/" + filename; irc->createDCCGet(this, filename, addr, port, size, new CallBack(this, &BuddyIcon::receivedIcon, strdup(filename.c_str()))); } catch(const DCCGetError&) { } } } } bool BuddyIcon::receivedIcon(void* data) { char* filename = static_cast(data); if(filename) im->setBuddyIcon(filename); b_log[W_SNO|W_INFO] << "New icon set!"; free(filename); return true; } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/cmds.cpp0000644000175000017500000003400511754151773015501 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "irc/settings.h" #include "irc/irc.h" #include "irc/user.h" #include "irc/channel.h" #include "server_poll/poll.h" #include "core/version.h" #include "core/util.h" namespace irc { /** PING [args ...] */ void IRC::m_ping(Message message) { message.setCommand(MSG_PONG); message.setSender(this); message.setReceiver(this); user->send(message); } /** PONG cookie */ void IRC::m_pong(Message message) { user->delFlag(Nick::PING); } /** NICK nickname */ void IRC::m_nick(Message message) { if(message.countArgs() < 1) user->send(Message(ERR_NONICKNAMEGIVEN).setSender(this) .setReceiver(user) .addArg("No nickname given")); else if(user->hasFlag(Nick::REGISTERED)) user->send(Message(ERR_NICKTOOFAST).setSender(this) .setReceiver(user) .addArg("The hand of the deity is upon thee, thy nick may not change")); else if(!Nick::isValidNickname(message.getArg(0))) user->send(Message(ERR_ERRONEUSNICKNAME).setSender(this) .setReceiver(user) .addArg("This nick contains invalid characters")); else { renameNick(user, message.getArg(0)); sendWelcome(); } } /** USER identname * * :realname*/ void IRC::m_user(Message message) { if(user->hasFlag(Nick::REGISTERED)) { user->send(Message(ERR_ALREADYREGISTRED).setSender(this) .setReceiver(user) .addArg("Please register only once per session")); return; } user->setIdentname(message.getArg(0)); user->setRealname(message.getArg(3)); sendWelcome(); } /** PASS passwd */ void IRC::m_pass(Message message) { string password = message.getArg(0); if(user->hasFlag(Nick::REGISTERED)) user->send(Message(ERR_ALREADYREGISTRED).setSender(this) .setReceiver(user) .addArg("Please register only once per session")); else if(password.size() < 8) quit("Password is too short (at least 8 characters)"); else if(password.find(' ') != string::npos) quit("Password may not contain spaces"); else user->setPassword(message.getArg(0)); } /** QUIT [message] */ void IRC::m_quit(Message message) { string reason = "Leaving..."; if(message.countArgs() >= 1) reason = message.getArg(0); quit("Quit: " + reason); } /** VERSION */ void IRC::m_version(Message message) { user->send(Message(RPL_VERSION).setSender(this) .setReceiver(user) .addArg(MINBIF_VERSION) .addArg(getServerName()) .addArg(MINBIF_BUILD)); } /** INFO */ void IRC::m_info(Message message) { for(size_t i = 0; infotxt[i] != NULL; ++i) user->send(Message(RPL_INFO).setSender(this) .setReceiver(user) .addArg(infotxt[i])); user->send(Message(RPL_ENDOFINFO).setSender(this) .setReceiver(user) .addArg("End of /INFO list.")); } /** ADMIN [key value] */ void IRC::m_admin(Message message) { assert(im != NULL); /* XXX It should does not work with several instances of IRC */ static struct { const char* key; bool display; SettingBase* setting; } settings[] = { { "password", true, new SettingPassword(this, im) }, { "typing_notice", true, new SettingTypingNotice(this, im) }, { "accept_nobuddies_messages", true, new SettingAcceptNoBuddiesMessages(this, im) }, { "send_delay", true, new SettingSendDelay(this, im) }, { "voiced_buddies", true, new SettingVoicedBuddies(this, im) }, { "server_aliases", true, new SettingServerAliases(this, im) }, { "away_idle", true, new SettingAwayIdle(this, im) }, { "log_level", true, new SettingLogLevel(this, im) }, { "proxy", true, new SettingProxy(this, im) }, { "proxy_host", true, new SettingProxyHost(this, im) }, { "proxy_port", true, new SettingProxyPort(this, im) }, { "proxy_user", true, new SettingProxyUsername(this, im) }, { "proxy_pass", true, new SettingProxyPassword(this, im) }, { "minbif", false, new SettingMinbif(this, im) }, }; if(message.countArgs() == 0) { for(unsigned i = 0; i < (sizeof settings / sizeof *settings); ++i) if(settings[i].display) user->send(Message(RPL_ADMINME).setSender(this) .setReceiver(user) .addArg(string("- ") + settings[i].key + " = " + settings[i].setting->getValue())); return; } unsigned i; for(i = 0; i < (sizeof settings / sizeof *settings) && message.getArg(0) != settings[i].key; ++i) ; if(i >= (sizeof settings / sizeof *settings)) { notice(user, "Key " + message.getArg(0) + " does not exist."); return; } if(message.countArgs() == 1) { user->send(Message(RPL_ADMINME).setSender(this) .setReceiver(user) .addArg(string("- ") + settings[i].key + " = " + settings[i].setting->getValue())); return; } vector args = message.getArgs(); string value; for(vector::iterator it = args.begin() + 1; it != args.end(); ++it) { if(!value.empty()) value += " "; value += *it; } settings[i].setting->setValue(value); user->send(Message(RPL_ADMINME).setSender(this) .setReceiver(user) .addArg(string("- ") + settings[i].key + " = " + settings[i].setting->getValue())); } /** MODE target [modes ..] */ void IRC::m_mode(Message message) { Message relayed(message.getCommand()); string target = message.getArg(0); relayed.setSender(user); for(size_t i = 1; i < message.countArgs(); ++i) relayed.addArg(message.getArg(i)); if(Channel::isChanName(target)) { Channel* c = getChannel(target); if(!c) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(target) .addArg("No suck channel")); return; } relayed.setReceiver(c); c->m_mode(user, relayed); } else { Nick* n = getNick(target); if(!n) { user->send(Message(ERR_NOSUCHNICK).setSender(this) .setReceiver(user) .addArg(target) .addArg("No suck nick")); return; } relayed.setReceiver(n); n->m_mode(user, relayed); } } /** AWAY [message] */ void IRC::m_away(Message message) { string away; if(message.countArgs()) away = message.getArg(0); if(im->setStatus(away)) { user->setAwayMessage(away); if(away.empty()) user->send(Message(RPL_UNAWAY).setSender(this) .setReceiver(user) .addArg("You are no longer marked as being away")); else user->send(Message(RPL_NOWAWAY).setSender(this) .setReceiver(user) .addArg("You have been marked as being away")); } } /* MOTD */ void IRC::m_motd(Message message) { user->send(Message(RPL_MOTDSTART).setSender(this).setReceiver(user).addArg("- " + getServerName() + " Message Of The Day -")); for(vector::iterator s = motd.begin(); s != motd.end(); ++s) user->send(Message(RPL_MOTD).setSender(this).setReceiver(user).addArg("- " + *s)); user->send(Message(RPL_ENDOFMOTD).setSender(this).setReceiver(user).addArg("End of /MOTD command.")); } /* OPER login password */ void IRC::m_oper(Message message) { if(user->hasFlag(Nick::OPER)) { user->send(Message(RPL_YOUREOPER).setSender(this) .setReceiver(user) .addArg("You are already an IRC Operator")); return; } vector opers = conf.GetSection("irc")->GetSectionClones("oper"); for(vector::iterator it = opers.begin(); it != opers.end(); ++it) { ConfigSection* oper = *it; if(oper->GetItem("login")->String() == message.getArg(0) && oper->GetItem("password")->String() == message.getArg(1)) { user->setFlag(Nick::OPER); user->send(Message(MSG_MODE).setSender(user) .setReceiver(user) .addArg("+o")); user->send(Message(RPL_YOUREOPER).setSender(this) .setReceiver(user) .addArg("You are now an IRC Operator")); poll->ipc_send(Message(MSG_OPER).addArg(user->getNickname())); return; } } user->send(Message(ERR_PASSWDMISMATCH).setSender(this) .setReceiver(user) .addArg("Password incorrect")); } /* WALLOPS :message */ void IRC::m_wallops(Message message) { if(!poll->ipc_send(Message(MSG_WALLOPS).addArg(getUser()->getNickname()) .addArg(message.getArg(0)))) { b_log[W_ERR] << "You're alone!"; } } /* REHASH */ void IRC::m_rehash(Message message) { getUser()->send(Message(RPL_REHASHING).setSender(this) .setReceiver(user) .addArg("Rehashing")); poll->rehash(); } /* DIE message */ void IRC::m_die(Message message) { if(!poll->ipc_send(Message(MSG_DIE).addArg(getUser()->getNickname()) .addArg(message.getArg(0)))) { b_log[W_INFO|W_SNO] << "This instance of MinBif is dying... Reason: " << message.getArg(0); quit("Shutdown requested: " + message.getArg(0)); } } /** STATS [p] */ void IRC::m_stats(Message message) { string arg = "*"; if(message.countArgs() > 0) arg = message.getArg(0); switch(arg[0]) { case 'a': for(unsigned i = 0; i < (unsigned)PURPLE_STATUS_NUM_PRIMITIVES; ++i) notice(user, string(purple_primitive_get_id_from_type((PurpleStatusPrimitive)i)) + ": " + purple_primitive_get_name_from_type((PurpleStatusPrimitive)i)); break; case 'c': { string accid = message.countArgs() > 1 ? message.getArg(1) : ""; im::Account account = im->getAccount(accid); if(!account.isValid() || !account.isConnected()) user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(accid) .addArg("No such account")); else { map m = account.getChatParameters(); for(map::iterator it = m.begin(); it != m.end(); ++it) notice(user, it->first + " = " + it->second); } break; } case 'm': for(size_t i = 0; commands[i].cmd != NULL; ++i) user->send(Message(RPL_STATSCOMMANDS).setSender(this) .setReceiver(user) .addArg(commands[i].cmd) .addArg(t2s(commands[i].count)) .addArg("0")); break; case 'o': { vector opers = conf.GetSection("irc")->GetSectionClones("oper"); for(vector::iterator it = opers.begin(); it != opers.end(); ++it) { ConfigSection* oper = *it; user->send(Message(RPL_STATSOLINE).setSender(this) .setReceiver(user) .addArg("O") .addArg(oper->GetItem("email")->String()) .addArg("*") .addArg(oper->GetItem("login")->String())); } break; } case 'p': { map m = im->getProtocolsList(); for(map::iterator it = m.begin(); it != m.end(); ++it) { im::Protocol proto = it->second; notice(user, proto.getID() + ": " + proto.getName()); } break; } case 'P': { map plugins = im->getPluginsList(); if (message.countArgs() < 2) { /* No argument */ for (map::iterator it = plugins.begin(); it != plugins.end(); ++it) { im::Plugin plug = it->second; string s = "["; if (plug.isLoaded()) s += "+"; else s += " "; s += "] "; s += plug.getID(); s += " ("; s += plug.getName() + ": " + plug.getSummary(); s += ")"; notice(user, s); } break; } map::iterator it = plugins.find(message.getArg(1)); if (it == plugins.end()) { notice(user, message.getArg(1) + ": No such plugin"); break; } im::Plugin plug = it->second; if (message.countArgs() < 3) { /* Only give a plugin name. */ notice(user, "-- Information about " + plug.getID()); notice(user, "Description: "); notice(user, plug.getDescription()); notice(user, string("Loaded: ") + (plug.isLoaded() ? "true" : "false")); break; } string command = message.getArg(2); switch(command[0]) { case 'l': try { plug.load(); notice(user, "Plugin '" + plug.getID() + "' loaded."); } catch (im::Plugin::LoadError &e) { notice(user, e.Reason()); } break; case 'u': try { plug.unload(); notice(user, "Plugin '" + plug.getID() + "' unloaded."); } catch(im::Plugin::LoadError &e) { notice(user, e.Reason()); } break; default: notice(user, "No suck command: " + command); break; } break; } case 'u': { unsigned now = time(NULL) - uptime; gchar *msg = g_strdup_printf("Server Up %d days, %d:%02d:%02d", now / 86400, (now / 3600) % 24, (now / 60) % 60, now % 60); user->send(Message(RPL_STATSUPTIME).setSender(this) .setReceiver(user) .addArg(msg)); g_free(msg); break; } default: arg = "*"; notice(user, "a (aways) - List all away messages availables"); notice(user, "c (chat params) - List all chat parameters for a specific account"); notice(user, "m (commands) - List all IRC commands"); notice(user, "o (opers) - List all opers accounts"); notice(user, "p (protocols) - List all protocols"); notice(user, "P (plugins) - List, load and configure plugins"); notice(user, "u (uptime) - Display the server uptime"); break; } user->send(Message(RPL_ENDOFSTATS).setSender(this) .setReceiver(user) .addArg(arg) .addArg("End of /STATS report")); } } /* ns irc */ minbif-1.0.5+git20120508/src/irc/channel.h0000644000175000017500000001404111754151773015626 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_CHANNEL_H #define IRC_CHANNEL_H #include #include #include "message.h" #include "core/entity.h" #include "core/util.h" namespace irc { using std::vector; using std::string; class Nick; class IRC; class Channel; /** This class represents a channel user. */ class ChanUser : public Entity { Nick* nick; Channel* chan; int status; public: /** Channel user modes */ enum mode_t { VOICE = PURPLE_CBFLAGS_VOICE, HALFOP = PURPLE_CBFLAGS_HALFOP, OP = PURPLE_CBFLAGS_OP, FOUNDER = PURPLE_CBFLAGS_FOUNDER, TYPING = PURPLE_CBFLAGS_TYPING, }; /** Structure used in arraw to convert a mode flag to char. */ static struct m2c_t { mode_t mode; char c; char prefix; } m2c[]; /** Build the ChanUser object. * * @param chan channel of this user * @param nick nick of this user * @param status status of user on channel */ ChanUser(Channel* chan, Nick* nick, int status = 0); string getName() const; string getLongName() const; bool hasStatus(int flag) const { return status & flag; } void setStatus(int flag) { status |= flag; } void delStatus(int flag) { status &= ~flag; } int getStatus() const { return status; } Nick* getNick() const { return nick; } Channel* getChannel() const { return chan; } /** Get status prefix (@+%) */ string getPrefix() const; /** Get the mode flag from char */ static mode_t c2mode(char c); /** Get the mode char from flag */ static char mode2c(mode_t m); /** Create Message object of modes * * @param add is it added or removed modes? * @param modes modes used. If = 0, use user modes * @return generated message */ Message getModeMessage(bool add, int modes = 0) const; }; /** This class represents an IRC Channel. */ class Channel : public Entity { protected: IRC* irc; private: vector users; string topic; public: static const char *CHMODES; /** Build the Channel object. * * @param irc the IRC object of main server * @param name channel name. */ Channel(IRC* irc, string name); virtual ~Channel(); /** Check the validity of a channel name * * @param name name to check * @return true if channel name is correct */ static bool isChanName(const string& name) { return (!name.empty() && name.find(' ') == string::npos && (isStatusChannel(name) || isRemoteChannel(name))); } static bool isStatusChannel(const string& name) { return (!name.empty() && name[0] == '&'); } static bool isRemoteChannel(const string& name) { return (!name.empty() && name[0] == '#'); } virtual bool isStatusChannel() const { return (!getName().empty() && getName()[0] == '&'); } virtual bool isRemoteChannel() const { return (!getName().empty() && getName()[0] == '#'); } /** Add a nick on channel. * * @param nick nick to add on channel * @param status initial status of user. * @return the ChanUser instance */ ChanUser* addUser(Nick* nick, int status=0); /** Remove an user from channel. * * @param nick user to remove * @param message optionnal message to send to all *other* channel users. */ virtual void delUser(Nick* nick, Message message = Message()); /** Count users on channel. */ size_t countUsers() const { return users.size(); } /** Get a vector of channel users. */ vector getChanUsers() const { return users; } /** Get a channel user. */ virtual ChanUser* getChanUser(string nick) const; /** Get topic */ virtual string getTopic() const { return topic; } /* Set topic on channel */ virtual bool setTopic(Entity* who, const string& topic); /** Mode message * * @param sender user who sent message * @param m mode message */ void m_mode(Nick* sender, Message m); /** Send NAMES reply to an user. * * @param nick the receiver of messages. */ void sendNames(Nick* nick) const; /** Send a command to this channel. */ virtual int sendCommand(const string& cmd) { return PURPLE_CMD_STATUS_WRONG_TYPE; } /** Invite someone into that channel. */ virtual bool invite(Nick* from, const string& nick, const string& message) = 0; /** Someone wants to kick someone else. */ virtual bool kick(ChanUser* from, ChanUser* who, const string& message) = 0; /** Show banlist to user */ virtual void showBanList(Nick* to) = 0; /** Process a ban add or remove * * @param from user who adds ban * @param pattern mask banned * @param add add or not a ban */ virtual void processBan(Nick* from, string pattern, bool add) = 0; /** Set a mode on a channel user. * * @param sender entity which sets mode. * @param modes flags set on channel. * @param chanuser channel user impacted. */ void setMode(const Entity* sender, int modes, ChanUser* chanuser); /** Remove a mode on a channel user. * * @param sender entity which removes mode. * @param modes flags set on channel. * @param chanuser channel user impacted. */ void delMode(const Entity* sender, int modes, ChanUser* chanuser); /** Broadcast a message to all channel users. * * @param m message sent to all channel users * @param butone optionnal user which will not receive message. */ virtual void broadcast(Message m, Nick* butone = NULL); }; }; /*namespace irc*/ #endif /* IRC_CHANNEL_H */ minbif-1.0.5+git20120508/src/irc/unknown_buddy.h0000644000175000017500000000311611754151773017105 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_UNKNOWN_BUDDY_H #define IRC_UNKNOWN_BUDDY_H #include "irc/conv_entity.h" namespace irc { /** This class represents an unknown buddy on IRC */ class UnknownBuddy : public ConvNick { public: /** Build buddy object * * @param server up-server * @param conv conversation object */ UnknownBuddy(Server* server, im::Conversation& conv); ~UnknownBuddy(); /** Implementation of the message routing to this buddy */ virtual void send(Message m); /** Get buddy's away message. */ virtual string getAwayMessage() const; /** Check if user is away or not. */ virtual bool isAway() const; /** Check if buddy is online or not. */ virtual bool isOnline() const { return true; } /** Get buddy's real name. */ virtual string getRealName() const; }; }; /* namespace irc */ #endif /* IRC_UNKNOWN_BUDDY_H */ minbif-1.0.5+git20120508/src/irc/settings.cpp0000644000175000017500000001341111754151773016411 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "settings.h" #include "im/im.h" #include "irc.h" #include "user.h" #include "core/util.h" #include "core/log.h" namespace irc { SettingBase::SettingBase(IRC* _irc, im::IM* _im) : irc(_irc), im(_im) {} string SettingPassword::getValue() const { return getIRC()->getIMAuth()->getPassword(); } bool SettingPassword::setValue(string v) { return getIRC()->getIMAuth()->setPassword(v); } string SettingTypingNotice::getValue() const { return getIM()->hasTypingNotice() ? "true" : "false"; } bool SettingTypingNotice::setValue(string v) { if(v == "1" || v == "true") getIM()->setTypingNotice(true); else if(v == "0" || v == "false") getIM()->setTypingNotice(false); else { getIRC()->notice(getIRC()->getUser(), "Value must be 'true' or 'false'"); return false; } return true; } string SettingAcceptNoBuddiesMessages::getValue() const { return getIM()->hasAcceptNoBuddiesMessages() ? "true" : "false"; } bool SettingAcceptNoBuddiesMessages::setValue(string v) { if(v == "1" || v == "true") getIM()->setAcceptNoBuddiesMessages(true); else if(v == "0" || v == "false") getIM()->setAcceptNoBuddiesMessages(false); else { getIRC()->notice(getIRC()->getUser(), "Value must be 'true' or 'false'"); return false; } return true; } string SettingSendDelay::getValue() const { return t2s(getIM()->getSendDelay()); } bool SettingSendDelay::setValue(string v) { getIM()->setSendDelay(s2t(v)); return true; } string SettingVoicedBuddies::getValue() const { return getIM()->hasVoicedBuddies() ? "true" : "false"; } bool SettingVoicedBuddies::setValue(string v) { if(v == "1" || v == "true") getIM()->setVoicedBuddies(true); else if(v == "0" || v == "false") getIM()->setVoicedBuddies(false); else { getIRC()->notice(getIRC()->getUser(), "Value must be 'true' or 'false'"); return false; } return true; } string SettingServerAliases::getValue() const { return getIM()->hasServerAliases() ? "true" : "false"; } bool SettingServerAliases::setValue(string v) { if(v == "1" || v == "true") getIM()->setServerAliases(true); else if(v == "0" || v == "false") getIM()->setServerAliases(false); else { getIRC()->notice(getIRC()->getUser(), "Value must be 'true' or 'false'"); return false; } return true; } string SettingAwayIdle::getValue() const { return getIM()->hasAwayIdle() ? "true" : "false"; } bool SettingAwayIdle::setValue(string v) { if(v == "1" || v == "true") getIM()->setAwayIdle(true); else if(v == "0" || v == "false") getIM()->setAwayIdle(false); else { getIRC()->notice(getIRC()->getUser(), "Value must be 'true' or 'false'"); return false; } return true; } string SettingMinbif::getValue() const { return "Minbif Is Not Bitlbee Fool"; } bool SettingMinbif::setValue(string) { getIRC()->notice(getIRC()->getUser(), "Wilmer, I see you!"); return false; } string SettingLogLevel::getValue() const { return b_log.formatLoggedFlags(); } bool SettingLogLevel::setValue(string s) { b_log.setLoggedFlags(s, b_log.toSyslog()); return true; } string SettingProxy::getValue() const { return purple_prefs_get_string("/purple/proxy/type"); } bool SettingProxy::setValue(string s) { static const char* l[] = {"none", "sock4", "sock5", "http", "envvar"}; for(unsigned i = 0; i < sizeof(l) / sizeof(*l); ++i) if(s == l[i]) { purple_prefs_set_string("/purple/proxy/type", s.c_str()); return true; } getIRC()->notice(getIRC()->getUser(), "Available values are: none, sock4, sock5, http, envvar"); return false; } string SettingProxyHost::getValue() const { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); const char* s = proxy ? purple_proxy_info_get_host(proxy) : ""; return s ? s : ""; } bool SettingProxyHost::setValue(string s) { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); if(proxy) { purple_proxy_info_set_host(proxy, s.c_str()); return true; } else return false; } string SettingProxyPort::getValue() const { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); return proxy ? t2s(purple_proxy_info_get_port(proxy)) : ""; } bool SettingProxyPort::setValue(string s) { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); if(proxy) { purple_proxy_info_set_port(proxy, s2t(s)); return true; } else return false; } string SettingProxyUsername::getValue() const { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); const char* s = proxy ? purple_proxy_info_get_username(proxy) : ""; return s ? s : ""; } bool SettingProxyUsername::setValue(string s) { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); if(proxy) { purple_proxy_info_set_username(proxy, s.c_str()); return true; } else return false; } string SettingProxyPassword::getValue() const { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); const char* s = proxy ? purple_proxy_info_get_password(proxy) : ""; return s ? s : ""; } bool SettingProxyPassword::setValue(string s) { PurpleProxyInfo* proxy = purple_global_proxy_get_info(); if(proxy) { purple_proxy_info_set_password(proxy, s.c_str()); return true; } else return false; } }; /* ns irc */ minbif-1.0.5+git20120508/src/irc/server.h0000644000175000017500000000371711754151773015534 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_SERVER_H #define IRC_SERVER_H #include #include "core/entity.h" #include "im/account.h" namespace irc { class IRC; class Nick; using std::vector; /** This class represents an IRC server */ class Server : public Entity { string info; vector users; public: /** Build the Server object. * * @param name server's name. * @param info string shown to describe this server. */ Server(string name, string info); string getServerName() const { return getName(); } string getServerInfo() const { return info; } void addNick(Nick* n); void removeNick(Nick* n); unsigned countNicks() const; unsigned countOnlineNicks() const; virtual IRC* getIRC() const = 0; }; /** This class represents a remote IRC server. * * A remote IRC server is a server linked to * an IM account. */ class RemoteServer : public Server { im::Account account; IRC* irc; public: /** Build the RemoteServer object. * * @param irc the IRC instance of main server. * @param account IM account linked to this server. */ RemoteServer(IRC* irc, im::Account account); IRC* getIRC() const { return irc; } im::Account getAccount() const { return account; } }; }; /* namespace irc */ #endif /* IRC_SERVER_H */ minbif-1.0.5+git20120508/src/irc/chat_buddy.cpp0000644000175000017500000000444111754151773016662 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "irc/irc.h" #include "irc/chat_buddy.h" #include "irc/server.h" #include "im/account.h" #include "core/util.h" namespace irc { ChatBuddy::ChatBuddy(Server* server, im::ChatBuddy _cbuddy) : ConvNick(server, im::Conversation(), "","","",""), im_cbuddy(_cbuddy) { string hostname = im_cbuddy.getRealName(); string identname = stringtok(hostname, "@"); string nickname = im_cbuddy.getName(); if(nickname.find('@') != string::npos || nickname.find(' ') != string::npos) nickname = nickize(stringtok(nickname, " @")); else nickname = nickize(nickname); if(hostname.empty()) hostname = im_cbuddy.getAccount().getID(); else hostname += ":" + im_cbuddy.getAccount().getID(); setNickname(nickname); setIdentname(identname); setHostname(hostname); } ChatBuddy::~ChatBuddy() { im::Conversation conv = getConversation(); if(conv.isValid() && conv.getNick() == this) conv.setNick(NULL); } void ChatBuddy::send(Message m) { if(m.getCommand() == MSG_PRIVMSG) { string text = m.getArg(0); if(m.getReceiver() == this) { if(!getConversation().isValid()) { im::Conversation conv = im::Conversation(im_cbuddy.getAccount(), im_cbuddy); conv.present(); conv.setNick(this); setConversation(conv); } enqueueMessage(text, getServer()->getIRC()->getIM()->getSendDelay()); } } } string ChatBuddy::getRealName() const { return im_cbuddy.getName(); } string ChatBuddy::getAwayMessage() const { /* TODO */ return ""; } bool ChatBuddy::isAway() const { /* TODO */ return false; } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/unknown_buddy.cpp0000644000175000017500000000407011754151773017440 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "irc/irc.h" #include "irc/unknown_buddy.h" #include "im/account.h" #include "core/util.h" namespace irc { UnknownBuddy::UnknownBuddy(Server* server, im::Conversation& conv) : ConvNick(server, conv, "","","","") { string hostname = conv.getName(); string identname = stringtok(hostname, "@"); string nickname = conv.getName(); if(nickname.find('@') != string::npos || nickname.find(' ') != string::npos) nickname = nickize(identname); else nickname = nickize(nickname); if(hostname.empty()) hostname = conv.getAccount().getID(); else hostname += ":" + conv.getAccount().getID(); setNickname(nickname); setIdentname(identname); setHostname(hostname); } UnknownBuddy::~UnknownBuddy() { im::Conversation conv = getConversation(); if(conv.isValid() && conv.getNick() == this) conv.setNick(NULL, false); } void UnknownBuddy::send(Message m) { if(m.getCommand() == MSG_PRIVMSG) { string text = m.getArg(0); if(m.getReceiver() == this && getConversation().isValid()) enqueueMessage(text, getServer()->getIRC()->getIM()->getSendDelay()); } } string UnknownBuddy::getRealName() const { return getConversation().getName(); } string UnknownBuddy::getAwayMessage() const { /* TODO */ return ""; } bool UnknownBuddy::isAway() const { /* TODO */ return false; } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/channel.cpp0000644000175000017500000001455211754151773016170 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "channel.h" #include "nick.h" #include "message.h" #include "irc.h" #include "core/util.h" namespace irc { const char *Channel::CHMODES = "qohvb"; ChanUser::ChanUser(Channel* _chan, Nick* _nick, int _status) : Entity(_nick->getNickname()), nick(_nick), chan(_chan), status(_status) { } string ChanUser::getName() const { return nick->getNickname(); } string ChanUser::getLongName() const { return nick->getLongName(); } ChanUser::m2c_t ChanUser::m2c[] = { { ChanUser::FOUNDER, 'q', '~' }, { ChanUser::OP, 'o', '@' }, { ChanUser::HALFOP, 'h', '%' }, { ChanUser::VOICE, 'v', '+' }, }; string ChanUser::getPrefix() const { string s; size_t i; for(i=0; i < sizeof m2c / sizeof *m2c; ++i) if(status & m2c[i].mode && m2c[i].prefix != '\0') s += m2c[i].prefix; return s; } ChanUser::mode_t ChanUser::c2mode(char c) { size_t i; for(i=0; i < sizeof m2c / sizeof *m2c && m2c[i].c != c; ++i) ; if(i < sizeof m2c / sizeof *m2c) return m2c[i].mode; else return (ChanUser::mode_t)0; } char ChanUser::mode2c(ChanUser::mode_t mode) { size_t i; for(i=0; i < sizeof m2c / sizeof *m2c && m2c[i].mode != mode; ++i) ; if(i < sizeof m2c / sizeof *m2c) return m2c[i].c; else return 0; } Message ChanUser::getModeMessage(bool add, int modes) const { Message message(MSG_MODE); string modes_str = add ? "+" : "-"; size_t i; if(!modes) modes = this->status; message.addArg(modes_str); for(i = 0; i < sizeof m2c / sizeof *m2c; ++i) if(m2c[i].mode & modes) { modes_str += m2c[i].c; message.addArg(getName()); } message.setArg(0, modes_str); return message; } Channel::Channel(IRC* _irc, string name) : Entity(name), irc(_irc) {} Channel::~Channel() { for(vector::iterator it = users.begin(); it != users.end(); ++it) { (*it)->getNick()->send(Message(MSG_PART).setSender(*it) .setReceiver(this)); (*it)->getNick()->removeChanUser(*it); delete *it; } users.clear(); } void Channel::sendNames(Nick* nick) const { string names; for(vector::const_iterator it = users.begin(); it != users.end(); ++it) { names += (*it)->getPrefix(); names += (*it)->getNick()->getNickname(); // We're detecting that a space exists before prepending : to arguments. // If we don't do it this way, a single-user channel won't prepend the colon to the // user list. // See Message::format() names += " "; } nick->send(Message(RPL_NAMREPLY).setSender(irc) .setReceiver(nick) .addArg("=") .addArg(getName()) .addArg(names)); nick->send(Message(RPL_ENDOFNAMES).setSender(irc) .setReceiver(nick) .addArg(getName()) .addArg("End of /NAMES list")); } ChanUser* Channel::addUser(Nick* nick, int status) { ChanUser* chanuser = new ChanUser(this, nick, status); users.push_back(chanuser); string names; for(vector::iterator it = users.begin(); it != users.end(); ++it) { (*it)->getNick()->send(Message(MSG_JOIN).setSender(nick).setReceiver(this)); if(status && (*it)->getNick() != nick) { Message m = chanuser->getModeMessage(true); m.setSender(irc); m.setReceiver(this); (*it)->getNick()->send(m); } } string topic = getTopic(); if(!topic.empty()) nick->send(Message(RPL_TOPIC).setSender(irc) .setReceiver(nick) .addArg(getName()) .addArg(topic)); sendNames(nick); return chanuser; } void Channel::delUser(Nick* nick, Message m) { for(vector::iterator it = users.begin(); it != users.end(); ) if((*it)->getNick() == nick) { delete *it; it = users.erase(it); } else { if(m.getCommand().empty() == false) (*it)->getNick()->send(m); ++it; } } ChanUser* Channel::getChanUser(string nick) const { /* Match is case sensitive */ nick = strlower(nick); for(vector::const_iterator it = users.begin(); it != users.end(); ++it) if(strlower((*it)->getNick()->getNickname()) == nick) return *it; return NULL; } void Channel::broadcast(Message m, Nick* butone) { for(vector::iterator it = users.begin(); it != users.end(); ++it) if(!butone || (*it)->getNick() != butone) (*it)->getNick()->send(m); } void Channel::m_mode(Nick* user, Message m) { if(m.countArgs() == 0) { user->send(Message(RPL_CHANNELMODEIS).setSender(irc) .setReceiver(user) .addArg(getName()) .addArg("+")); user->send(Message(RPL_CREATIONTIME).setSender(irc) .setReceiver(user) .addArg(getName()) .addArg("1212313")); return; } vector args = m.getArgs(); vector::iterator arg = args.begin(); string modes = *arg++; bool add = true; FOREACH(string, modes, c) { switch(*c) { case '+': add = true; break; case '-': add = false; break; case 'b': if(arg == args.end()) showBanList(user); else processBan(user, *arg++, add); break; default: break; } } } void Channel::setMode(const Entity* sender, int modes, ChanUser* chanuser) { if(!modes) return; chanuser->setStatus(modes); Message m = chanuser->getModeMessage(true, modes); m.setSender(sender ? sender : irc); m.setReceiver(this); broadcast(m); } void Channel::delMode(const Entity* sender, int modes, ChanUser* chanuser) { if(!modes) return; chanuser->delStatus(modes); Message m = chanuser->getModeMessage(false, modes); m.setSender(sender ? sender : irc); m.setReceiver(this); broadcast(m); } bool Channel::setTopic(Entity* chanuser, const string& topic) { if (this->topic == topic) return true; this->topic = topic; broadcast(Message(MSG_TOPIC).setSender(chanuser ? chanuser : irc) .setReceiver(this) .addArg(topic)); return true; } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/cmds_accounts.cpp0000644000175000017500000002564611754151773017413 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "irc/irc.h" #include "irc/user.h" #include "irc/status_channel.h" namespace irc { /** CONNECT servername */ void IRC::m_connect(Message message) { bool found = false; string target = message.getArg(0); map accounts = im->getAccountsList(); for(map::iterator it = accounts.begin(); it != accounts.end(); ++it) { im::Account& account = it->second; if(target == "*" || account.getID() == target || account.getServername() == target) { found = true; account.connect(); Channel* chan = account.getStatusChannel(); if(chan) user->join(chan, ChanUser::OP); } } if(!found && target != "*") notice(user, "Error: Account " + target + " is unknown"); } /** SQUIT servername */ void IRC::m_squit(Message message) { bool found = false; string target = message.getArg(0); map accounts = im->getAccountsList(); for(map::iterator it = accounts.begin(); it != accounts.end(); ++it) { im::Account& account = it->second; if(target == "*" || account.getID() == target || account.getServername() == target) { found = true; account.disconnect(); } } if(!found && target != "*") notice(user, "Error: Account " + target + " is unknown"); } bool IRC::m_map_registeradd(Message& message, im::Account& added_account, bool register_account) { /* XXX Probably not needed. */ message.rebuildWithQuotes(); if(message.countArgs() < 2) { notice(user, "Usage: /MAP add PROTO USERNAME [OPTIONS]"); notice(user, "To display available options: /MAP add PROTO"); notice(user, "Use '/STATS p' to show all available protocols"); return false; } string protoname = message.getArg(1); im::Protocol proto; try { proto = im->getProtocol(protoname); } catch(im::ProtocolUnknown &e) { notice(user, "Error: Protocol " + protoname + " is unknown. Try '/STATS p' to list protocols."); return false; } im::Protocol::Options options = proto.getOptions(); if(message.countArgs() < 3) { string s; FOREACH(im::Protocol::Options, options, it) { if(!s.empty()) s += " "; s += "[-"; switch(it->second.getType()) { case im::Protocol::Option::SERVER_ALIASES: case im::Protocol::Option::BOOL: s += "[!]" + it->second.getName(); break; case im::Protocol::Option::ACCID: case im::Protocol::Option::STATUS_CHANNEL: case im::Protocol::Option::PASSWORD: case im::Protocol::Option::STR: s += it->second.getName() + " value"; break; case im::Protocol::Option::INT: s += it->second.getName() + " int"; break; case im::Protocol::Option::NONE: break; } s += "]"; } notice(user, "Usage: /MAP add " + proto.getID() + " USERNAME " + s); return false; } string username; try { for(size_t i = 2; i < message.countArgs(); ++i) { string s = message.getArg(i); if(username.empty()) username = purple_url_decode(s.c_str()); /* static buffer, does not need free */ else if(s[0] == '-') { size_t name_pos = 1; string value = "true"; if(s[1] == '!') { value = "false"; name_pos++; } im::Protocol::Options::iterator it = options.find(s.substr(name_pos)); if (it == options.end()) { notice(user, "Error: Option '" + s + "' does not exist"); return false; } switch(it->second.getType()) { case im::Protocol::Option::BOOL: case im::Protocol::Option::SERVER_ALIASES: /* No input value needed, already got above */ break; default: if(i+1 < message.countArgs()) value = message.getArg(++i); else { notice(user, "Error: Option '" + s + "' needs a value"); return false; } } it->second.setValue(value); } else if (options["password"].getValue().empty()) options["password"].setValue(s); else options["status_channel"].setValue(s); } added_account = im->addAccount(proto, username, options, register_account); } catch (im::Protocol::OptionError& e) { notice(user, "Error: " + e.Reason()); return false; } return true; } bool IRC::m_map_add(Message& message, im::Account& added_account) { return m_map_registeradd(message, added_account, false); } bool IRC::m_map_register(Message& message, im::Account& added_account) { return m_map_registeradd(message, added_account, true); } bool IRC::m_map_edit(Message& message, im::Account&) { if(message.countArgs() < 2) { notice(user, "Usage: /MAP edit ACCOUNT [KEY [VALUE]]"); return false; } im::Account account = im->getAccount(message.getArg(1)); if(!account.isValid()) { notice(user, "Error: Account " + message.getArg(1) + " is unknown"); return false; } im::Protocol::Options options = account.getOptions(); if(message.countArgs() < 3) { notice(user, "-- Parameters of account " + account.getServername() + " --"); FOREACH(im::Protocol::Options, options, it) { im::Protocol::Option& option = it->second; string value; switch(option.getType()) { case im::Protocol::Option::PASSWORD: value = "*******"; break; default: value = option.getValue(); break; } notice(user, option.getName() + " = " + value); } return false; } im::Protocol::Options::iterator option; for(option = options.begin(); option != options.end() && option->second.getName() != message.getArg(2); ++option) ; if(option == options.end()) { notice(user, "Key " + message.getArg(2) + " does not exist."); return false; } if(message.countArgs() < 4) notice(user, option->second.getName() + " = " + option->second.getValue()); else { string value; for(unsigned i = 3; i < message.countArgs(); ++i) { if(!value.empty()) value += " "; value += message.getArg(i); } if(option->second.getType() == im::Protocol::Option::BOOL && value != "true" && value != "false") { notice(user, "Error: Option '" + option->second.getName() + "' is a boolean ('true' or 'false')"); return false; } /* TODO check if value is an integer if option is an integer */ option->second.setValue(value); if(option->second.getType() == im::Protocol::Option::INT) notice(user, option->second.getName() + " = " + t2s(option->second.getValueInt())); else notice(user, option->second.getName() + " = " + option->second.getValue()); try { account.setOptions(options); } catch(im::Protocol::OptionError& e) { notice(user, "Error: " + e.Reason()); } } return false; } bool IRC::m_map_delete(Message& message, im::Account&) { if(message.countArgs() != 2) { notice(user, "Usage: /MAP delete ACCOUNT"); return false; } im::Account account = im->getAccount(message.getArg(1)); if (!account.isValid()) { notice(user, "Error: Account " + message.getArg(1) + " is unknown"); return false; } notice (user, "Removing account "+account.getUsername()); im->delAccount(account); return true; } bool IRC::m_map_command(Message& message, im::Account&) { if(message.countArgs() < 2) { notice(user, "Usage: /MAP cmd ACCOUNT [command]"); return false; } im::Account account = im->getAccount(message.getArg(1)); if(!account.isValid()) { notice(user, "Error: Account " + message.getArg(1) + " is unknown"); return false; } if(!account.isConnected()) { notice(user, "Error: Account " + message.getArg(1) + " is not connected"); return false; } if(message.countArgs() < 3) { vector cmds = account.getCommandsList(); notice(user, "Commands available for " + message.getArg(1) + ":"); for(vector::iterator it = cmds.begin(); it != cmds.end(); ++it) notice(user, " " + *it); } else if (!account.callCommand(message.getArg(2))) notice(user, "Command " + message.getArg(2) + " is unknown"); return false; } bool IRC::m_map_help(Message& message, im::Account&) { notice(user,"add PROTO USERNAME PASSWD [CHANNEL] [options] add an account"); notice(user,"reg PROTO USERNAME PASSWD [CHANNEL] [options] add an account and register it on server"); notice(user,"edit ACCOUNT [KEY [VALUE]] edit an account"); notice(user,"cmd ACCOUNT [COMMAND] run a command on an account"); notice(user,"delete ACCOUNT remove ACCOUNT from your accounts"); notice(user,"help display this message"); notice(user,"Usage: /MAP add|register|edit|command|delete|help [...]"); return false; } /** MAP */ void IRC::m_map(Message message) { im::Account added_account; if(message.countArgs() > 0) { string arg = message.getArg(0); static const struct { const char *name; bool (IRC::*func) (Message& message, im::Account& account); } commands[] = { { "register", &IRC::m_map_register }, { "add", &IRC::m_map_add }, { "edit", &IRC::m_map_edit }, { "delete", &IRC::m_map_delete }, { "command", &IRC::m_map_command }, { "cmd", &IRC::m_map_command }, { "help", &IRC::m_map_help }, }; size_t i; for (i = 0; i < (sizeof(commands)/sizeof(*commands)) && string(commands[i].name).find(arg) != 0; ++i) ; if (i >= (sizeof(commands)/sizeof(*commands))) { m_map_help(message, added_account); return; } if (!(this->*commands[i].func)(message, added_account)) return; } user->send(Message(RPL_MAP).setSender(this) .setReceiver(user) .addArg(this->getServerName())); map accounts = im->getAccountsList(); for(map::iterator it = accounts.begin(); it != accounts.end(); ++it) { map::iterator next = it; Server* server = getServer(it->second.getServername()); string name; if(++next == accounts.end()) name = "`"; else name = "|"; if(it->second == added_account) name += "-+"; else if(it->second.isConnecting()) name += "-*"; else if(it->second.isConnected()) name += "-"; else name += " "; name += it->second.getServername(); if(it->second.isConnected()) name += " [" + t2s(server->countOnlineNicks()) + "/" + t2s(server->countNicks()) + "]"; user->send(Message(RPL_MAP).setSender(this) .setReceiver(user) .addArg(name)); } user->send(Message(RPL_MAPEND).setSender(this) .setReceiver(user) .addArg("End of /MAP")); } } /* ns irc */ minbif-1.0.5+git20120508/src/irc/user.h0000644000175000017500000000361311754151773015177 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_USER_H #define IRC_USER_H #include "nick.h" #include "sockwrap/sockwrap.h" namespace irc { /** This class represents user connected to minbif */ class User : public Nick { sock::SockWrapper* sockw; string password; time_t last_read; public: /** Build the User object. * * @param _sockw socket wrapper used to write messages to user * @param server up-server (probably an IRC instance) * @param nickname nickname of user * @param identname identname of user * @param hostname hostname of user * @param realname realname of user */ User(sock::SockWrapper* _sockw, Server* server, string nickname, string identname, string hostname, string realname=""); ~User(); void setPassword(string p) { password = p; } string getPassword() const { return password; } void close() { sockw = NULL; } string getModes() const; virtual void m_mode(Nick* sender, Message m); /** Set last read timestamp to now */ void setLastReadNow(); time_t getLastRead() const { return last_read; } /** Send a message to file descriptor */ virtual void send(Message m); }; }; /* namespace irc */ #endif /* IRC_USER_H */ minbif-1.0.5+git20120508/src/irc/replies.h0000644000175000017500000001076711754151773015674 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_REPLIES_H #define IRC_REPLIES_H #define RPL_WELCOME "001" #define RPL_YOURHOST "002" #define RPL_CREATED "003" #define RPL_MYINFO "004" #define RPL_ISUPPORT "005" #define RPL_MAP "015" #define RPL_MAPEND "017" #define RPL_STATSCOMMANDS "212" #define RPL_ENDOFSTATS "219" #define RPL_UMODEIS "221" #define RPL_STATSUPTIME "242" #define RPL_STATSOLINE "243" #define RPL_LUSERCLIENT "251" #define RPL_LUSEROP "252" #define RPL_LUSERUNKNOWN "253" #define RPL_LUSERCHANNELS "254" #define RPL_LUSERME "255" #define RPL_ADMINME "256" #define RPL_AWAY "301" #define RPL_ISON "303" #define RPL_UNAWAY "305" #define RPL_NOWAWAY "306" #define RPL_WHOISUSER "311" #define RPL_WHOISSERVER "312" #define RPL_WHOISOPERATOR "313" #define RPL_ENDOFWHO "315" #define RPL_ENDOFWHOIS "318" #define RPL_WHOISCHANNELS "319" #define RPL_WHOISACTUALLY "320" #define RPL_LISTSTART "321" #define RPL_LIST "322" #define RPL_LISTEND "323" #define RPL_CHANNELMODEIS "324" #define RPL_CREATIONTIME "329" #define RPL_NOTOPIC "331" #define RPL_TOPIC "332" #define RPL_INVITING "341" #define RPL_VERSION "351" #define RPL_WHOREPLY "352" #define RPL_NAMREPLY "353" #define RPL_ENDOFNAMES "366" #define RPL_BANLIST "367" #define RPL_ENDOFBANLIST "368" #define RPL_ENDOFWHOWAS "369" #define RPL_INFO "371" #define RPL_MOTD "372" #define RPL_ENDOFINFO "374" #define RPL_MOTDSTART "375" #define RPL_ENDOFMOTD "376" #define RPL_YOUREOPER "381" #define RPL_REHASHING "382" #define ERR_NOSUCHNICK "401" #define ERR_NOSUCHCHANNEL "403" #define ERR_WASNOSUCHNICK "406" #define ERR_UNKNOWNCOMMAND "421" #define ERR_NONICKNAMEGIVEN "431" #define ERR_ERRONEUSNICKNAME "432" #define ERR_NICKNAMEINUSE "433" #define ERR_NICKTOOFAST "438" #define ERR_NOTONCHANNEL "442" #define ERR_NOTREGISTERED "451" #define ERR_NEEDMOREPARAMS "461" #define ERR_ALREADYREGISTRED "462" #define ERR_PASSWDMISMATCH "464" #define ERR_CHANFORWARDING "470" #define ERR_NOPRIVILEGES "481" #define ERR_CHANOPRIVSNEEDED "482" #define ERR_UMODEUNKNOWNFLAG "501" #define MSG_PRIVMSG "PRIVMSG" #define MSG_NOTICE "NOTICE" #define MSG_MODE "MODE" #define MSG_JOIN "JOIN" #define MSG_PART "PART" #define MSG_QUIT "QUIT" #define MSG_ERROR "ERROR" #define MSG_NICK "NICK" #define MSG_PING "PING" #define MSG_PONG "PONG" #define MSG_USER "USER" #define MSG_PASS "PASS" #define MSG_VERSION "VERSION" #define MSG_INFO "INFO" #define MSG_WHO "WHO" #define MSG_WHOIS "WHOIS" #define MSG_WHOWAS "WHOWAS" #define MSG_STATS "STATS" #define MSG_CONNECT "CONNECT" #define MSG_SCONNECT "SCONNECT" #define MSG_SQUIT "SQUIT" #define MSG_MAP "MAP" #define MSG_ADMIN "ADMIN" #define MSG_LIST "LIST" #define MSG_ISON "ISON" #define MSG_INVITE "INVITE" #define MSG_KICK "KICK" #define MSG_KILL "KILL" #define MSG_SVSNICK "SVSNICK" #define MSG_TOPIC "TOPIC" #define MSG_NAMES "NAMES" #define MSG_AWAY "AWAY" #define MSG_MOTD "MOTD" #define MSG_WALLOPS "WALLOPS" #define MSG_REHASH "REHASH" #define MSG_DIE "DIE" #define MSG_OPER "OPER" #define MSG_CMD "CMD" #endif /* IRC_REPLIES_H */ minbif-1.0.5+git20120508/src/irc/settings.h0000644000175000017500000000334411754151773016062 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_SETTINGS_H #define IRC_SETTINGS_H #include namespace im { class IM; }; namespace irc { using std::string; class IRC; class SettingBase { IRC* irc; im::IM* im; protected: im::IM* getIM() const { return im; } IRC* getIRC() const { return irc; } public: SettingBase(IRC* irc, im::IM* im); virtual ~SettingBase() {} virtual string getValue() const = 0; virtual bool setValue(string v) = 0; }; #define SETTING(x) \ class Setting##x : public SettingBase \ { \ public: \ Setting##x(IRC* irc, im::IM* im) : SettingBase(irc, im) {} \ virtual string getValue() const; \ virtual bool setValue(string v); \ } SETTING(Password); SETTING(TypingNotice); SETTING(AcceptNoBuddiesMessages); SETTING(SendDelay); SETTING(VoicedBuddies); SETTING(ServerAliases); SETTING(AwayIdle); SETTING(Minbif); SETTING(LogLevel); SETTING(Proxy); SETTING(ProxyHost); SETTING(ProxyPort); SETTING(ProxyUsername); SETTING(ProxyPassword); }; /* ns irc */ #endif /* IRC_SETTINGS_H */ minbif-1.0.5+git20120508/src/irc/nick.cpp0000644000175000017500000001372611754151773015506 0ustar sebseb/* * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "irc/nick.h" #include "irc/server.h" #include "irc/channel.h" #include "core/caca_image.h" #include "core/util.h" namespace irc { const char *Nick::nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|"; const char *Nick::nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\"; const char *Nick::UMODES = "o"; Nick::Nick(Server* _server, string nickname, string _identname, string _hostname, string _realname) : Entity(nickname), server(_server), flags(0) { assert(server); setIdentname(_identname); setHostname(_hostname); setRealname(_realname); } Nick::~Nick() { quit("*.net *.split"); } bool Nick::isValidNickname(const string& nick) { /* Empty/long nicks are not allowed, nor numbers at [0] */ if(nick.empty() || isdigit(nick[0]) || nick.size() > Nick::MAX_LENGTH) return false; for(string::const_iterator i = nick.begin(); i != nick.end(); ++i) if(!strchr(Nick::nick_lc_chars, *i) && !strchr(Nick::nick_uc_chars, *i)) return false; return true; } string Nick::nickize(const string& n) { string nick; /* Transliterate string. */ GIConv ic = g_iconv_open("ASCII//TRANSLIT", "UTF-8"); gchar* conv = g_convert_with_iconv(n.c_str(), n.size(), ic, NULL, NULL, NULL); for(const char* c = conv ? conv : n.c_str(); *c; ++c) if(strchr(nick_lc_chars, *c) || strchr(nick_uc_chars, *c)) nick += *c; g_iconv_close(ic); g_free(conv); if(isdigit(nick[0])) nick = "_" + nick; if(nick.size() > MAX_LENGTH) nick = nick.substr(0, MAX_LENGTH); /* Empty nicknames are not allowed. */ if (nick.empty()) nick = "Invalid"; return nick; } void Nick::setNickname(string n) { setName(n); } void Nick::setIdentname(string n) { for(string::iterator i = n.begin(); i != n.end(); ++i) if(*i == ' ') *i = '_'; identname = n; } void Nick::setHostname(string n) { for(string::iterator i = n.begin(); i != n.end(); ++i) if(*i == ' ') *i = '.'; hostname = n; } string Nick::getLongName() const { return getNickname() + "!" + getIdentname() + "@" + getHostname(); } CacaImage Nick::getIcon() const { return CacaImage(); } vector Nick::getChannels() const { return channels; } bool Nick::isOn(const Channel* chan) const { for(vector::const_iterator it = channels.begin(); it != channels.end(); ++it) if((*it)->getChannel() == chan) return true; return false; } ChanUser* Nick::getChanUser(const Channel* chan) const { for(vector::const_iterator it = channels.begin(); it != channels.end(); ++it) if((*it)->getChannel() == chan) return *it; return NULL; } ChanUser* Nick::join(Channel* chan, int status) { ChanUser* chanuser; if((chanuser = getChanUser(chan))) { int last_status = chanuser->getStatus(); if(last_status != status) { chan->delMode(NULL, last_status & ~status, chanuser); chan->setMode(NULL, status & ~last_status, chanuser); } return chanuser; } chanuser = chan->addUser(this, status); channels.push_back(chanuser); return chanuser; } void Nick::part(Channel* chan, string message) { Message m = Message(MSG_PART).setSender(this) .setReceiver(chan) .addArg(message); for(vector::iterator it = channels.begin(); it != channels.end();) if((*it)->getChannel() == chan) { ChanUser* chanuser = *it; it = channels.erase(it); send(m); chanuser->getChannel()->delUser(this, m); break; } else ++it; } void Nick::removeChanUser(ChanUser* chanuser) { for(vector::iterator it = channels.begin(); it != channels.end();) if(*it == chanuser) it = channels.erase(it); else ++it; } void Nick::kicked(Channel* chan, ChanUser* from, string message) { for(vector::iterator it = channels.begin(); it != channels.end();) if((*it)->getChannel() == chan) { ChanUser* cu = *it; it = channels.erase(it); cu->getChannel()->delUser(this, Message(MSG_KICK).setSender(from) .setReceiver(cu->getChannel()) .addArg(getNickname()) .addArg(message)); } else ++it; } void Nick::quit(string text) { Message m = Message(MSG_QUIT).setSender(this) .addArg(text); vector sended; for(vector::iterator it = channels.begin(); it != channels.end();) { vector users = (*it)->getChannel()->getChanUsers(); FOREACH(vector, users, u) { Nick* n = (*u)->getNick(); if(std::find(sended.begin(), sended.end(), n) == sended.end()) { sended.push_back(n); n->send(m); } } (*it)->getChannel()->delUser(this); it = channels.erase(it); } } void Nick::privmsg(Channel* chan, string msg) { string tmp; while((tmp = stringtok(msg, "\n\r")).empty() == false) chan->broadcast(Message(MSG_PRIVMSG).setSender(this) .setReceiver(chan) .addArg(tmp), this); } void Nick::privmsg(Nick* nick, string msg) { string tmp; while((tmp = stringtok(msg, "\n\r")).empty() == false) nick->send(Message(MSG_PRIVMSG).setSender(this) .setReceiver(nick) .addArg(tmp)); } void Nick::m_mode(Nick* user, Message m) { user->send(Message(ERR_NOPRIVILEGES).setSender(getServer()) .setReceiver(user) .addArg("Permission Denied: Insufficient privileges")); } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/dcc.cpp0000644000175000017500000002246711754151773015315 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include "dcc.h" #include "nick.h" #include "message.h" #include "core/callback.h" #include "core/util.h" #include "core/log.h" #include "core/config.h" namespace irc { DCCServer::DCCServer(string _type, string _filename, size_t _total_size, Nick* _sender, Nick* _receiver) : type(_type), filename(_filename), total_size(_total_size), start_time(time(NULL)), sender(_sender), receiver(_receiver), listen_data(NULL), watcher(0), fd(-1), port(0), finished(false) { ConfigItem* item = conf.GetSection("file_transfers")->GetItem("port_range"); listen_data = purple_network_listen_range((uint16_t)item->MinInteger(), (uint16_t)item->MaxInteger(), SOCK_STREAM, &DCCServer::listen_cb, this); if(!listen_data) throw DCCListenError(); } DCCServer::~DCCServer() { deinit(); } void DCCServer::deinit() { if(fd >= 0) close(fd); if(watcher > 0) purple_input_remove(watcher); if(listen_data != NULL) purple_network_listen_cancel(listen_data); finished = true; fd = -1; watcher = 0; listen_data = NULL; } void DCCServer::dcc_read_cb(gpointer data, int source, PurpleInputCondition cond) { DCCServer* dcc = static_cast(data); dcc->dcc_read(source); } void DCCServer::connected(gpointer data, int source, PurpleInputCondition cond) { DCCServer* dcc = static_cast(data); int conn = accept(dcc->fd, NULL, 0), flags; if(!conn) { b_log[W_ERR] << "DCC connection failed"; dcc->deinit(); return; } purple_input_remove(dcc->watcher); dcc->watcher = 0; close(dcc->fd); dcc->fd = conn; flags = fcntl(conn, F_GETFL); fcntl(conn, F_SETFL, flags | O_NONBLOCK); fcntl(conn, F_SETFD, FD_CLOEXEC); dcc->watcher = purple_input_add(conn, PURPLE_INPUT_READ, DCCServer::dcc_read_cb, dcc); dcc->updated(false); } void DCCServer::listen_cb(int sock, void* data) { DCCServer* dcc = static_cast(data); struct in_addr addr; string bind_addr = conf.GetSection("file_transfers")->GetItem("dcc_own_ip")->String(); if (bind_addr == " ") bind_addr = purple_network_get_my_ip(-1); dcc->fd = sock; dcc->listen_data = NULL; dcc->port = purple_network_get_port_from_fd(sock); if (!inet_aton(bind_addr.c_str(), &addr)) { b_log[W_ERR] << "Unable to parse this IP address: [" << bind_addr << "]: Unable to send DCC request."; dcc->deinit(); return; } dcc->watcher = purple_input_add(sock, PURPLE_INPUT_READ, connected, dcc); /* As there isn't any way to escape correctly strings in the DCC SEND * sequence, it replaces every '"' with a '\''. */ string filename = dcc->filename; for(string::iterator c = filename.begin(); c != filename.end(); ++c) if(*c == '"') *c = '\''; dcc->receiver->send(Message(MSG_PRIVMSG).setSender(dcc->sender ? dcc->sender->getLongName() : "some.one") .setReceiver(dcc->receiver) .addArg("\001DCC " + dcc->type + " \"" + dcc->filename + "\" " + t2s(ntohl(addr.s_addr)) + " " + t2s(dcc->port) + (dcc->total_size ? (" " + t2s(dcc->total_size)) : "") + "\001")); } DCCSend::DCCSend(const im::FileTransfert& _ft, Nick* _sender, Nick* _receiver) : DCCServer("SEND", _ft.getFileName(), _ft.getSize(), _sender, _receiver), ft(_ft), local_filename(_ft.getLocalFileName()), bytes_sent(0), fp(NULL), rxlen(0), rxqueue(NULL) { } DCCSend::~DCCSend() { deinit(); } void DCCSend::deinit() { DCCServer::deinit(); if(fp != NULL) fclose(fp); g_free(rxqueue); fp = NULL; rxqueue = NULL; } void DCCSend::updated(bool destroy) { if(destroy) this->ft = im::FileTransfert(); /* No-valid object */ if((fd < 0 || listen_data) && (start_time + TIMEOUT < time(NULL))) deinit(); else dcc_send(); } void DCCSend::dcc_send() { if(finished || listen_data || fd < 0) return; if(!fp) { fp = fopen(local_filename.c_str(), "r"); if(!fp) return; /* File isn't written yet. */ } /* It does not add any \0 on buffer */ static char buf[512]; size_t len = fread(buf, sizeof(char), sizeof buf, fp); if(len > 0) send(fd, buf, len, 0); bytes_sent += len; } void DCCSend::dcc_read(int source) { static char buffer[64]; ssize_t len; len = read(source, buffer, sizeof(buffer)); if (len < 0 && errno == EAGAIN) return; else if (len <= 0) { /* DCC user has closed connection. * fd is already closed, do not let deinit() * reclose it. */ this->fd = -1; this->deinit(); return; } this->rxqueue = (guchar*)g_realloc(this->rxqueue, len + this->rxlen); memcpy(this->rxqueue + this->rxlen, buffer, len); this->rxlen += (guint)len; while (1) { size_t acked; if (this->rxlen < 4) break; acked = ntohl(*((gint32 *)this->rxqueue)); this->rxlen -= 4; if (this->rxlen) { unsigned char *tmp = (unsigned char*)g_memdup(this->rxqueue + 4, (guint)this->rxlen); g_free(this->rxqueue); this->rxqueue = tmp; } else { g_free(this->rxqueue); this->rxqueue = NULL; } if (acked >= this->total_size) { /* DCC send terminated \o/ */ this->deinit(); return; } } this->dcc_send(); } DCCChat::DCCChat(Nick* sender, Nick* receiver) : DCCServer("CHAT", "CHAT", 0, sender, receiver) {} DCCChat::~DCCChat() { } void DCCChat::deinit() { DCCServer::deinit(); } void DCCChat::dcc_read(int source) { /* Don't care */ } void DCCChat::updated(bool destroy) { if((fd < 0 || listen_data) && (start_time + TIMEOUT < time(NULL))) deinit(); } void DCCChat::dcc_send(string buf) { if(finished || listen_data || fd < 0) return; send(fd, buf.c_str(), buf.size(), 0); } DCCGet::DCCGet(Nick* _from, string _filename, uint32_t addr, uint16_t port, ssize_t size, _CallBack* _cb) : from(_from), filename(_filename), callback(_cb), finished(false), sock(-1), watcher(0), fp(NULL), bytes_received(0), total_size(size) { fp = fopen(filename.c_str(), "w"); if(!fp) { b_log[W_ERR] << "Unable to create local file: " << filename; throw DCCGetError(); } struct sockaddr_in fsocket; memset(&fsocket, 0, sizeof fsocket); fsocket.sin_family = AF_INET; fsocket.sin_addr.s_addr = htonl(addr); fsocket.sin_port = htons(port); sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(connect(sock, (struct sockaddr*) &fsocket, sizeof fsocket) < 0) { b_log[W_ERR] << "Unable to receive file: " << strerror(errno); delete callback; throw DCCGetError(); } watcher = purple_input_add(sock, PURPLE_INPUT_READ, DCCGet::dcc_read, this); } DCCGet::~DCCGet() { deinit(); } void DCCGet::deinit() { if(sock >= 0) close(sock); if(watcher > 0) purple_input_remove(watcher); if(fp != NULL) fclose(fp); if(callback) delete callback; finished = true; sock = -1; watcher = 0; fp = NULL; callback = NULL; } void DCCGet::dcc_read(gpointer data, int source, PurpleInputCondition cond) { DCCGet* dcc = static_cast(data); static char buffer[1024]; ssize_t len; len = read(source, buffer, sizeof(buffer)); if (len < 0 && errno == EAGAIN) return; else if (len <= 0) { /* DCC user has closed connection. * fd is already closed, do not let deinit() * reclose it. */ dcc->sock = -1; dcc->deinit(); return; } if(fwrite(buffer, sizeof(char), len, dcc->fp) < (size_t)len) { b_log[W_ERR] << "Unable to write received data: " << strerror(errno); dcc->deinit(); return; } dcc->bytes_received += len; if(dcc->bytes_received >= dcc->total_size) { unsigned long l = htonl(dcc->bytes_received); size_t result = write(dcc->sock, &l, sizeof(l)); if(result != sizeof(l)) b_log[W_WARNING] << "Unable to send DCC ack"; if(dcc->callback) { fflush(dcc->fp); dcc->callback->run(); } dcc->deinit(); } } void DCCGet::updated(bool destroy) { /* I don't care about. */ } void DCCGet::setPeer(Nick* n) { /* IRC user left, can remove transfert. */ from = n; if(from) /* I think this never happens... */ callback->setObj(from); else deinit(); } bool DCCGet::parseDCCSEND(string line, string* filename, uint32_t* addr, uint16_t* port, ssize_t* size) { if(line[0] != '\1' || line[line.size() - 1] != '\1') return false; string word; Message args; /* Remove \1 chars. */ line = line.substr(1, line.size()-2); while((word = stringtok(line, " ")).empty() == false) args.addArg(word); args.rebuildWithQuotes(); if(args.countArgs() == 6 && args.getArg(0) == "DCC" && args.getArg(1) == "SEND") { *filename = args.getArg(2); *addr = s2t(args.getArg(3)); *port = s2t(args.getArg(4)); *size = s2t(args.getArg(5)); return true; } return false; } } /* namespace irc */ minbif-1.0.5+git20120508/src/irc/buddy.cpp0000644000175000017500000001263211754151773015664 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "irc/buddy.h" #include "irc/server.h" #include "irc/channel.h" #include "irc/status_channel.h" #include "irc/dcc.h" #include "irc/irc.h" #include "im/im.h" #include "im/account.h" #include "core/callback.h" #include "core/log.h" #include "core/util.h" #include "core/config.h" namespace irc { Buddy::Buddy(Server* server, im::Buddy _buddy) : ConvNick(server, im::Conversation(), "","","",_buddy.getRealName()), im_buddy(_buddy), public_msgs(false), public_msgs_last(0) { string hostname = im_buddy.getName(); string identname = stringtok(hostname, "@"); string nickname = im_buddy.getAlias(); if(nickname.find('@') != string::npos || nickname.find(' ') != string::npos) nickname = nickize(identname); else nickname = nickize(nickname); if(hostname.empty()) hostname = im_buddy.getAccount().getID(); else hostname += ":" + im_buddy.getAccount().getID(); setNickname(nickname); setIdentname(identname); setHostname(hostname); im_buddy.setNick(this); } Buddy::~Buddy() { setConversation(NULL); /* eventually purge the pointer to me in conv */ if(im_buddy.isValid()) im_buddy.setNick(NULL); } void Buddy::send(Message m) { if(m.getCommand() == MSG_PRIVMSG) { string text = m.getArg(0); const Channel* chan = dynamic_cast(m.getReceiver()); if(m.getReceiver() == this || (chan && chan->isStatusChannel() && text.find(getNickname() + ": ") == 0)) { if(chan && chan->isStatusChannel()) { public_msgs = true; stringtok(text, " "); } else public_msgs = false; public_msgs_last = time(NULL); /* Check if this is a DCC SEND message. */ if(process_dcc_get(text)) return; check_conv(); enqueueMessage(text, getServer()->getIRC()->getIM()->getSendDelay()); } } } void Buddy::sendMessage(Nick* to, const string& t, bool action) { Channel* chan; if (public_msgs_last + Buddy::PUBLIC_MSGS_TIMEOUT < time(NULL)) public_msgs = false; if (public_msgs && (chan = im_buddy.getAccount().getStatusChannel())) { string line = t; if (action) line = "\001ACTION " + to->getNickname() + ": " + line + "\001"; else line = to->getNickname() + ": " + line; to->send(irc::Message(MSG_PRIVMSG).setSender(this) .setReceiver(chan) .addArg(line)); } else ConvNick::sendMessage(to, t, action); } void Buddy::check_conv(void) { if(!getConversation().isValid()) { im::Conversation conv = im::Conversation(im_buddy.getAccount(), im_buddy); conv.present(); conv.setNick(this); setConversation(conv); } } void Buddy::setConversation(const im::Conversation& c) { im::Conversation conv = getConversation(); if (conv.isValid() && conv.getNick() == this) conv.setNick(NULL); ConvNick::setConversation(c); } bool Buddy::process_dcc_get(const string& text) { if(conf.GetSection("file_transfers")->GetItem("enabled")->Boolean() == false) { b_log[W_ERR] << "File transfers are disabled on this server."; return true; } string filename; uint32_t addr; uint16_t port; ssize_t size; if(DCCGet::parseDCCSEND(text, &filename, &addr, &port, &size)) { RemoteServer* rm = dynamic_cast(getServer()); if(!rm) return true; string path = rm->getIRC()->getIM()->getUserPath() + "/upload/"; if(!check_write_file(path, filename)) { b_log[W_ERR] << "Unable to write into the upload directory '" << path << "': " << strerror(errno); return true; } try { filename = path + "/" + filename; rm->getIRC()->createDCCGet(this, filename, addr, port, size, new CallBack(this, &Buddy::received_file, strdup(filename.c_str()))); } catch(const DCCGetError&) { } return true; } return false; } bool Buddy::received_file(void* data) { char* filename = static_cast(data); im_buddy.sendFile(filename); free(filename); return true; } string Buddy::getRealName() const { return im_buddy.getRealName() + " [Group: " + im_buddy.getGroupName() + "]"; } string Buddy::getStatusMessage(bool away) const { if (!away && isAway()) return ""; return im_buddy.getStatus(); } string Buddy::getAwayMessage() const { if(im_buddy.isOnline() == false) return "User is offline"; return im_buddy.getStatus(); } bool Buddy::isAway() const { return im_buddy.isOnline() == false || im_buddy.isAvailable() == false; } bool Buddy::isOnline() const { return im_buddy.isOnline(); } CacaImage Buddy::getIcon() const { return im_buddy.getIcon(); } string Buddy::getIconPath() const { return im_buddy.getIconPath(); } bool Buddy::retrieveInfo() const { im_buddy.retrieveInfo(); return true; } int Buddy::sendCommand(const string& cmd) { check_conv(); return getConversation().sendCommand(cmd); } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/conversation_channel.h0000644000175000017500000000426111754151773020423 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_CONVERSATION_CHANNEL_H #define IRC_CONVERSATION_CHANNEL_H #include #include "irc/channel.h" #include "irc/server.h" #include "irc/conv_entity.h" #include "im/conversation.h" namespace irc { using std::map; class ChatBuddy; class ConversationChannel : public Channel, public ConvEntity { RemoteServer* upserver; map cbuddies; public: ConversationChannel(IRC* irc, const im::Conversation& conv); virtual ~ConversationChannel(); virtual bool isStatusChannel() const { return false; } virtual bool isRemoteChannel() const { return true; } virtual void showBanList(Nick* to); virtual void processBan(Nick* from, string pattern, bool add); virtual void broadcast(Message m, Nick* butone = NULL); virtual ChanUser* getChanUser(string nick) const; ChanUser* getChanUser(const im::ChatBuddy& cb) const; void addBuddy(im::ChatBuddy cbuddy, int status = 0); void updateBuddy(im::ChatBuddy cbuddy); void renameBuddy(ChanUser* chanuser, im::ChatBuddy cbuddy); virtual void delUser(Nick* nick, Message message = Message()); virtual string getTopic() const; virtual int sendCommand(const string& cmd); virtual bool invite(Nick* from, const string& nickname, const string& message); virtual bool kick(ChanUser* from, ChanUser* victim, const string& message); virtual bool setTopic(Entity* from, const string& message); }; }; /* ns irc */ #endif /* IRC_CONVERSATION_CHANNEL_H */ minbif-1.0.5+git20120508/src/irc/conv_entity.h0000644000175000017500000000333211754151773016560 0ustar sebseb/* * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_CONV_ENTITY_H #define IRC_CONV_ENTITY_H #include "im/conversation.h" #include "irc/nick.h" namespace irc { class Server; class ConvEntity { im::Conversation conv; vector enqueued_messages; bool flush_messages(void* data); public: ConvEntity(im::Conversation conv); /** Get the conversation associated to this nick */ virtual im::Conversation getConversation() const { return conv; } /** Set the conversation associated. */ virtual void setConversation(const im::Conversation& c) { conv = c; } /** Enqueue message to send to conversation. */ virtual void enqueueMessage(const string& text, int delay); }; class ConvNick : public Nick, public ConvEntity { public: ConvNick(Server* server, im::Conversation conv, string nickname, string identname, string hostname, string realname = ""); /** The ConvNick sends a message to someone. */ virtual void sendMessage(Nick* to, const string& text, bool action = false); }; }; /* ns irc */ #endif /* IRC_CONV_ENTITY_H */ minbif-1.0.5+git20120508/src/irc/chat_buddy.h0000644000175000017500000000330711754151773016327 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_CHAT_BUDDY_H #define IRC_CHAT_BUDDY_H #include "irc/conv_entity.h" namespace irc { /** This class represents a chat buddy on IRC */ class ChatBuddy : public ConvNick { im::ChatBuddy im_cbuddy; public: /** Build buddy object * * @param server up-server * @param cbuddy IM buddy object. */ ChatBuddy(Server* server, im::ChatBuddy cbuddy); ~ChatBuddy(); /** Implementation of the message routing to this buddy */ virtual void send(Message m); /** Get buddy's away message. */ virtual string getAwayMessage() const; /** Check if user is away or not. */ virtual bool isAway() const; /** Check if buddy is online or not. */ virtual bool isOnline() const { return true; } im::ChatBuddy getChatBuddy() const { return im_cbuddy; } void setChatBuddy(im::ChatBuddy cb) { im_cbuddy = cb; } /** Get buddy's real name. */ virtual string getRealName() const; }; }; /* namespace irc */ #endif /* IRC_CHAT_BUDDY_H */ minbif-1.0.5+git20120508/src/irc/status_channel.h0000644000175000017500000000340611754151773017234 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_STATUS_CHANNEL_H #define IRC_STATUS_CHANNEL_H #include #include "channel.h" #include "im/account.h" namespace irc { using std::vector; class StatusChannel : public Channel { vector accounts; string getMaskFromName(const string& name, const im::Account& acc) const; public: StatusChannel(IRC* irc, string name); virtual ~StatusChannel() {} virtual bool isStatusChannel() const { return true; } virtual bool isRemoteChannel() const { return false; } void addAccount(const im::Account& account); void removeAccount(const im::Account& account); size_t countAccounts() const { return accounts.size(); } virtual bool invite(Nick* from, const string& nickname, const string& message); virtual bool kick(ChanUser* from, ChanUser* victim, const string& message); virtual bool setTopic(Entity* who, const string& message); virtual void showBanList(Nick* to); virtual void processBan(Nick* from, string pattern, bool add); }; }; /* ns irc */ #endif /* IRC_STATUS_CHANNEL_H */ minbif-1.0.5+git20120508/src/irc/nick.h0000644000175000017500000001213111754151773015140 0ustar sebseb/* * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_NICK_H #define IRC_NICK_H #include #include #include "core/entity.h" #include "irc/message.h" class CacaImage; namespace irc { class Server; class Channel; class ChanUser; using std::map; /** This class represents a nick on network. * * This is a base class. */ class Nick : public Entity { string identname, hostname, realname; string away; Server* server; unsigned int flags; vector channels; public: static const char *nick_lc_chars; static const char *nick_uc_chars; static const size_t MAX_LENGTH = 29; static const char* UMODES; static string nickize(const string& n); /** States of the user */ enum { REGISTERED = 1 << 0, PING = 1 << 1, OPER = 1 << 2 }; /** Build the Nick object. * * @param server up-server of this nick * @param nickname nickname of nick * @param identname ident of nick * @param hostname hostname of nick * @param realname realname */ Nick(Server* server, string nickname, string identname, string hostname, string realname=""); virtual ~Nick(); /** Check if the given string is a valid nickname. */ static bool isValidNickname(const string& n); /** Virtual method called when sending a message to this nick. */ virtual void send(Message m) {} /** User joins a channel * * @param chan channel to join * @param status status on channel (see ChanUser) */ ChanUser* join(Channel* chan, int status = 0); /** User leaves a channel * * @param chan channel to leave * @param message part message */ void part(Channel* chan, string message=""); /** Remove an ChanUser from list. * * @param chanuser ChanUser object */ void removeChanUser(ChanUser* chanuser); /** User quits network * * @param message quit message. */ void quit(string message=""); /** Used has been kicked by someone else on a channel. * * @param chan channel * @param kicker bad guy who kicked me * @param reason reason invoked to kick me */ void kicked(Channel* chan, ChanUser* kicker, string reason); /** User sends a privmsg to a channel * * @param chan channel target * @param message text message */ void privmsg(Channel* chan, string message); /** User sends a privmsg to an other user. * * @param to user target * @param message text message */ void privmsg(Nick* to, string message); /** Send a command to this user. */ virtual int sendCommand(const string& cmd) { return PURPLE_CMD_STATUS_WRONG_TYPE; } /** Mode message * * @param sender user who sent message * @param m mode message */ virtual void m_mode(Nick* sender, Message m); /** Get all channels user is on. */ vector getChannels() const; /** Check if user is on one specified channel. * * @param chan channel to check * @return true if user is on channel. */ bool isOn(const Channel* chan) const; /** Get ChanUser object of a channel. * * @param chan channel * @return ChanUser instance if user is on channel, NULL othewise */ ChanUser* getChanUser(const Channel* chan) const; Server* getServer() const { return server; } /** Get the full name representation of user. * * @return a string in form "nick!ident@hostname" */ virtual string getLongName() const; string getNickname() const { return getName(); } void setNickname(string n); string getIdentname() const { return identname; } void setIdentname(string i); string getHostname() const { return hostname; } void setHostname(string h); virtual string getRealName() const { return realname; } void setRealname(string r) { realname = r; } virtual bool retrieveInfo() const { return false; } /** Get status. * @param away if false, do not return anything if the buddy is away. */ virtual string getStatusMessage(bool away = false) const { return ""; } void setFlag(unsigned flag) { flags |= flag; } void delFlag(unsigned flag) { flags &= ~flag; } bool hasFlag(unsigned flag) const { return flags & flag; } void setAwayMessage(string a) { away = a; } virtual string getAwayMessage() const { return away; } virtual bool isAway() const { return away.empty() == false; } virtual bool isOnline() const { return true; } virtual CacaImage getIcon() const; virtual string getIconPath() const { return ""; } }; }; /* namespace irc */ #endif /* IRC_NICK_H */ minbif-1.0.5+git20120508/src/irc/irc.h0000644000175000017500000001616211754151773015001 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2010 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_IRC_H #define IRC_IRC_H #include #include #include #include #include "message.h" #include "server.h" #include "im/auth.h" #include "sockwrap/sockwrap.h" #include "core/exception.h" class _CallBack; class ServerPoll; namespace im { class IM; class Account; class Buddy; class Conversation; class FileTransfert; }; /** IRC related classes */ namespace irc { using std::string; using std::map; class User; class Nick; class ConvNick; class Buddy; class Channel; class DCC; STREXCEPTION(IRCError); /** This class represents the user's server. * * It provides all user's commands handlers, and several * mechanismes to control the IRC network. */ class IRC : public Server { ServerPoll* poll; sock::SockWrapper* sockw; _CallBack *read_cb; int ping_id; time_t ping_freq; time_t uptime; _CallBack *ping_cb; User* user; im::IM* im; im::Auth *im_auth; map users; map channels; map servers; vector dccs; vector motd; struct command_t { const char* cmd; void (IRC::*func)(Message); size_t minargs; unsigned count; unsigned flags; }; static command_t commands[]; void cleanUpNicks(); void cleanUpChannels(); void cleanUpServers(); void cleanUpDCC(); /** Callback when it receives a new incoming message from socket. */ bool readIO(void*); bool check_channel_join(void*); void m_nick(Message m); /**< Handler for the NICK message */ void m_user(Message m); /**< Handler for the USER message */ void m_pass(Message m); /**< Handler for the PASS message */ void m_quit(Message m); /**< Handler for the QUIT message */ void m_ping(Message m); /**< Handler for the PING message */ void m_pong(Message m); /**< Handler for the PONG message */ void m_who(Message m); /**< Handler for the WHO message */ void m_whois(Message m); /**< Handler for the WHOIS message */ void m_whowas(Message m); /**< Handler for the WHOWAS message */ void m_version(Message m); /**< Handler for the VERSION message */ void m_info(Message m); /**< Handler for the INFO message */ void m_privmsg(Message m); /**< Handler for the PRIVMSG message */ void m_stats(Message m); /**< Handler for the STATS message */ void m_connect(Message m); /**< Handler for the CONNECT message */ void m_squit(Message m); /**< Handler for the SQUIT message */ void m_map(Message m); /**< Handler for the MAP message */ bool m_map_registeradd(Message& m, im::Account&, bool); bool m_map_register(Message& m, im::Account&); bool m_map_add(Message& m, im::Account&); bool m_map_edit(Message& m, im::Account&); bool m_map_delete(Message& m, im::Account&); bool m_map_command(Message& m, im::Account&); bool m_map_help(Message& m, im::Account&); void m_admin(Message m); /**< Handler for the ADMIN message */ void m_join(Message m); /**< Handler for the JOIN message */ void m_part(Message m); /**< Handler for the PART message */ void m_list(Message m); /**< Handler for the LIST message */ void m_mode(Message m); /**< Handler for the MODE message */ void m_names(Message m); /**< Handler for the NAMES message */ void m_topic(Message m); /**< Handler for the TOPIC message */ void m_ison(Message m); /**< Handler for the ISON message */ void m_invite(Message m); /**< Handler for the INVITE message */ void m_kick(Message m); /**< Handler for the KICK message */ void m_kill(Message m); /**< Handler for the KILL message */ void m_svsnick(Message m); /**< Handler for the SVSNICK message */ void m_away(Message m); /**< Handler for the AWAY message */ void m_motd(Message m); /**< Handler for the MOTD message */ void m_oper(Message m); /**< Handler for the OPER message */ void m_wallops(Message m); /**< Handler for the WALLOPS message */ void m_rehash(Message m); /**< Handler for the REHASH message */ void m_die(Message m); /**< Handler for the DIE message */ void m_cmd(Message m); /**< Handler for the CMD message */ public: /** Create an instance of the IRC class * * @param poll the server poll used by minbif * @param _sockw socket wrapper where read and write to user * @param hostname server's hostname * @param ping_freq frequence of pings */ IRC(ServerPoll* poll, sock::SockWrapper* _sockw, string hostname, unsigned ping_freq); ~IRC(); User* getUser() const { return user; } virtual IRC* getIRC() const { return (IRC*)this; } im::IM* getIM() const { return im; } im::Auth* getIMAuth() const { return im_auth; } /** Ends the auth sequence. * * It checks if user has sent all requested parameters to * authenticate himself, and checks for password. * * If authentification success, it create the im::IM instance, * sends all welcome replies, create account servers, etc. */ void sendWelcome(); /** User quits. * * @param reason text used in the QUIT message */ void quit(string reason = ""); sock::SockWrapper* getSockWrap() const { return sockw; }; void addChannel(Channel* chan); Channel* getChannel(string channame) const; void removeChannel(string channame); void rehash(bool verbose = true); void setMotd(const string& path); void addNick(Nick* nick); Nick* getNick(string nick, bool case_sensitive = false) const; Buddy* getNick(const im::Buddy& buddy) const; ConvNick* getNick(const im::Conversation& c) const; vector matchNick(string pattern) const; void removeNick(string nick); void renameNick(Nick* n, string newnick); void addServer(Server* server); Server* getServer(string server) const; void removeServer(string server); DCC* createDCCSend(const im::FileTransfert& ft, Nick* from); DCC* createDCCGet(Nick* from, string filename, uint32_t addr, uint16_t port, ssize_t size, _CallBack* callback); void updateDCC(const im::FileTransfert& ft, bool destroy = false); /** Callback used by glibc to check user ping */ bool ping(void*); /** Send a notice to a user. * * @param user destination * @param message text message sent */ void notice(Nick* user, string message); /** Send a privmsg to a user. * * @param user destination * @param message text message sent. */ void privmsg(Nick* user, string message); }; }; /* namespace irc */ #endif /* IRC_IRC_H */ minbif-1.0.5+git20120508/src/irc/message.cpp0000644000175000017500000000655211754151773016205 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "irc/message.h" #include "irc/irc.h" #include "irc/nick.h" #include "irc/channel.h" #include "core/util.h" #include "core/entity.h" namespace irc { string Message::StoredEntity::getName() const { return entity ? entity->getName() : name; } string Message::StoredEntity::getLongName() const { return entity ? entity->getLongName() : name; } Message::Message(string _cmd) : cmd(_cmd) { } Message::~Message() { } string Message::format() const { string buf; if(sender.isSet()) buf = ":" + sender.getLongName() + " "; buf += cmd; if(receiver.isSet()) buf += " " + receiver.getName(); for(vector::const_iterator it = args.begin(); it != args.end(); ++it) { buf += " "; if(it->find(' ') != string::npos || it->c_str()[0] == ':') buf += ":"; buf += *it; } buf += "\r\n"; return buf; } Message& Message::setCommand(string r) { assert (r.empty() == false); cmd = r; return *this; } Message& Message::setSender(const Entity* entity) { sender.setEntity(entity); return *this; } Message& Message::setSender(string n) { sender.setName(n); return *this; } Message& Message::setReceiver(const Entity* entity) { receiver.setEntity(entity); return *this; } Message& Message::setReceiver(string n) { receiver.setName(n); return *this; } Message& Message::addArg(string s) { if(!args.empty() && args.back().find(' ') != string::npos) throw MalformedMessage(); args.push_back(s); return *this; } Message& Message::setArg(size_t i, string s) { if(i == args.size()) return addArg(s); assert(i < args.size()); if((i+1) < args.size() && args.back().find(' ') != string::npos) throw MalformedMessage(); args[i] = s; return *this; } string Message::getArg(size_t n) const { assert(n < args.size()); return args[n]; } Message Message::parse(string line) { string s; Message m; while((s = stringtok(line, " ")).empty() == false) { if(m.getCommand().empty()) m.setCommand(strupper(s)); else if(s[0] == ':') { m.addArg(s.substr(1) + (line.empty() ? "" : " " + line)); break; } else m.addArg(s); } return m; } void Message::rebuildWithQuotes() { for(vector::iterator s = args.begin(); s != args.end(); ++s) { if((*s)[0] == '"' && s->find(' ') == string::npos) { vector::iterator next = s; *s = s->substr(1); while((*next)[next->size()-1] != '"') { if(next == s) ++next; else next = args.erase(next); if(next == args.end()) break; *s += " " + *next; } if((*s)[s->size()-1] == '"') *s = s->substr(0, s->size()-1); if(next == args.end()) break; else if(next != s) next = args.erase(next); } } } }; /* namespace irc */ minbif-1.0.5+git20120508/src/irc/message.h0000644000175000017500000000433011754151773015642 0ustar sebseb/* * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef IRC_MESSAGE_H #define IRC_MESSAGE_H #include #include #include #include "irc/replies.h" class Entity; namespace irc { using std::string; using std::vector; class MalformedMessage : public std::exception {}; class Message { class StoredEntity { const Entity* entity; string name; public: StoredEntity() : entity(NULL) {} void setEntity(const Entity* e) { entity = e; name.clear(); } void setName(string n) { name = n; entity = NULL; } bool isSet() const { return entity || !name.empty(); } const Entity* getEntity() const { return entity; } string getName() const; string getLongName() const; }; string cmd; StoredEntity sender; StoredEntity receiver; vector args; public: Message(string command); Message() {} ~Message(); Message& setCommand(string command); Message& setSender(const Entity* entity); Message& setSender(string name); Message& setReceiver(const Entity* entity); Message& setReceiver(string name); Message& addArg(string); Message& setArg(size_t, string); string getCommand() const { return cmd; } const Entity* getSender() const { return sender.getEntity(); } const Entity* getReceiver() const { return receiver.getEntity(); } string getArg(size_t n) const; size_t countArgs() const { return args.size(); } vector getArgs() const { return args; } string format() const; void rebuildWithQuotes(); static Message parse(string s); }; }; /* namespace irc */ #endif /* IRC_MESSAGE_H */ minbif-1.0.5+git20120508/src/irc/cmds_channels.cpp0000644000175000017500000004610411754151773017357 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2011 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "core/caca_image.h" #include "irc/irc.h" #include "irc/user.h" #include "irc/buddy.h" #include "irc/channel.h" namespace irc { /** WHO */ void IRC::m_who(Message message) { string arg; Channel* chan = NULL; enum { WHO_STATUS = 1 << 0 }; #define fset(v, b, f) v = b ? v|f : v&(~f) int flags = 0; if(message.countArgs() > 0) { vector args = message.getArgs(); for(vector::iterator it = args.begin(); it != args.end(); ++it) { bool add = true; if (!strchr("+-", (*it)[0])) { arg = *it; continue; } for (string::iterator c = it->begin(); c != it->end(); ++c) switch (*c) { case '+': add = true; break; case '-': add = false; break; case 's': fset(flags, add, WHO_STATUS); break; } } } #undef fset if(arg.empty() || !Channel::isChanName(arg) || (chan = getChannel(arg))) for(std::map::iterator it = users.begin(); it != users.end(); ++it) { Nick* n = it->second; string channame = "*"; if(arg.empty() || arg == "*" || arg == "0" || n->getServer()->getServerName().find(arg) != string::npos || !fnmatch(arg.c_str(), n->getNickname().c_str(), FNM_NOESCAPE|FNM_CASEFOLD)) { vector chans = n->getChannels(); if(!chans.empty()) channame = chans.front()->getChannel()->getName(); } else if(chan) { if(!n->isOn(chan)) continue; channame = arg; } else continue; user->send(Message(RPL_WHOREPLY).setSender(this) .setReceiver(user) .addArg(channame) .addArg(n->getIdentname()) .addArg(n->getHostname()) .addArg(n->getServer()->getServerName()) .addArg(n->getNickname()) .addArg(n->isAway() ? "G" : "H") .addArg("0 " + (flags & WHO_STATUS ? n->getStatusMessage(true) : n->getRealName()))); } user->send(Message(RPL_ENDOFWHO).setSender(this) .setReceiver(user) .addArg(!arg.empty() ? arg : "**") .addArg("End of /WHO list")); } /** WHOIS nick */ void IRC::m_whois(Message message) { Nick* n = getNick(message.getArg(0)); if(!n) { user->send(Message(ERR_NOSUCHNICK).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("Nick does not exist")); return; } bool extended_whois = false; if(message.countArgs() > 1) extended_whois = true; user->send(Message(RPL_WHOISUSER).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg(n->getIdentname()) .addArg(n->getHostname()) .addArg("*") .addArg(n->getRealName())); vector chanusers = n->getChannels(); string chans; FOREACH(vector, chanusers, chanuser) { if(chans.empty() == false) chans += " "; chans += (*chanuser)->getChannel()->getName(); } if(chans.empty() == false) user->send(Message(RPL_WHOISCHANNELS).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg(chans)); user->send(Message(RPL_WHOISSERVER).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg(n->getServer()->getServerName()) .addArg(n->getServer()->getServerInfo())); if(n->isAway()) user->send(Message(RPL_AWAY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg(n->getAwayMessage())); string status = n->getStatusMessage(); if (!status.empty()) user->send(Message(RPL_WHOISACTUALLY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg(status)); if(n->hasFlag(Nick::OPER)) user->send(Message(RPL_WHOISOPERATOR).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg("is an IRC Operator")); CacaImage icon = n->getIcon(); try { string buf = icon.getIRCBuffer(0, extended_whois ? 15 : 10); string line; user->send(Message(RPL_WHOISACTUALLY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg("Icon:")); while((line = stringtok(buf, "\r\n")).empty() == false) { user->send(Message(RPL_WHOISACTUALLY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg(line)); } } catch(CacaError &e) { user->send(Message(RPL_WHOISACTUALLY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg("No icon")); } catch(CacaNotLoaded &e) { user->send(Message(RPL_WHOISACTUALLY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg("libcaca and imlib2 are required to display icon")); } string url = conf.GetSection("irc")->GetItem("buddy_icons_url")->String(); string icon_path = n->getIconPath(); if(url != " " && !icon_path.empty()) { icon_path = icon_path.substr(im->getUserPath().size()); user->send(Message(RPL_WHOISACTUALLY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg("Icon URL: " + url + im->getUsername() + icon_path)); } /* Retrieve server info about this buddy only if this is an extended * whois. In this case, do not send a ENDOFWHOIS because this * is an asynchronous call. */ if(!extended_whois || !n->retrieveInfo()) user->send(Message(RPL_ENDOFWHOIS).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg("End of /WHOIS list")); } /** WHOWAS nick * * As irsii tries a whowas when whois fails and waits for answer... */ void IRC::m_whowas(Message message) { user->send(Message(ERR_WASNOSUCHNICK).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("Nick does not exist")); user->send(Message(RPL_ENDOFWHOWAS).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("End of WHOWAS")); } /** PRIVMSG target message */ void IRC::m_privmsg(Message message) { Message relayed(message.getCommand()); string targets = message.getArg(0), target; while ((target = stringtok(targets, ",")).empty() == false) { relayed.setSender(user); relayed.addArg(message.getArg(1)); if(Channel::isChanName(target)) { Channel* c = getChannel(target); if(!c) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(target) .addArg("No suck channel")); return; } relayed.setReceiver(c); c->broadcast(relayed, user); } else { Nick* n = getNick(target); if(!n) { user->send(Message(ERR_NOSUCHNICK).setSender(this) .setReceiver(user) .addArg(target) .addArg("No suck nick")); return; } relayed.setReceiver(n); n->send(relayed); if(n->isAway()) user->send(Message(RPL_AWAY).setSender(this) .setReceiver(user) .addArg(n->getNickname()) .addArg(n->getAwayMessage())); } } } /** JOIN channame */ void IRC::m_join(Message message) { string names = message.getArg(0); string channame; string parameters = message.countArgs() > 1 ? message.getArg(1) : ""; while((channame = stringtok(names, ",")).empty() == false) { if(!Channel::isChanName(channame)) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(channame) .addArg("No such channel")); continue; } switch(channame[0]) { case '&': { Channel* chan = getChannel(channame); if(!chan) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(channame) .addArg("No such channel")); continue; } user->join(chan, ChanUser::OP); break; } case '#': { Channel* chan = getChannel(channame); /* Channel already exists, I'm really probably in. */ if(chan) continue; string accid = channame.substr(1); /* purple_url_decode() returns a static buffer, no free needed. */ string convname = purple_url_decode(stringtok(accid, ":").c_str()); string param = purple_url_decode(stringtok(parameters, ",").c_str()); if(accid.empty() || convname.empty()) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(channame) .addArg("No such channel")); continue; } im::Account account = im->getAccount(accid); if(!account.isValid()) user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(channame) .addArg("No such channel")); else if(!account.isConnected()) account.enqueueChannelJoin(convname); else if(!account.joinChat(convname, param)) user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(channame) .addArg("No such channel")); else g_timeout_add(1000, g_callback_delete, new CallBack(this, &IRC::check_channel_join, g_strdup(channame.c_str()))); #if 0 /* wait the signal from libpurple to create the conversation channel. */ chan = new ConversationChannel(this, conv); user->send(Message(ERR_CHANFORWARDING).setSender(this) .setReceiver(user) .addArg(channame) .addArg(chan->getName()) .addArg("Forwarding to another channel")); user->join(chan); #endif break; } default: user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(channame) .addArg("No such channel")); break; } } } bool IRC::check_channel_join(void* data) { char* name = static_cast(data); if(!getChannel(name)) user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(name) .addArg("No such channel")); g_free(data); return false; } /** PART chan [:message] */ void IRC::m_part(Message message) { string channame = message.getArg(0); string reason = ""; if(message.countArgs() > 1) reason = message.getArg(1); Channel* chan = getChannel(channame); if(!chan) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(channame) .addArg("No such channel")); return; } user->part(chan, reason); } /** LIST */ void IRC::m_list(Message message) { if(message.countArgs() == 0) { user->send(Message(RPL_LISTSTART).setSender(this) .setReceiver(user) .addArg("Channel") .addArg("Users Name")); for(map::iterator it = channels.begin(); it != channels.end(); ++it) user->send(Message(RPL_LIST).setSender(this) .setReceiver(user) .addArg(it->second->getName()) .addArg(t2s(it->second->countUsers()))); user->send(Message(RPL_LISTEND).setSender(this) .setReceiver(user) .addArg("End of /LIST")); } else { im::Account account = im->getAccount(message.getArg(0)); if(!account.isValid() || !account.isConnected()) user->send(Message(RPL_LISTEND).setSender(this) .setReceiver(user) .addArg("End of /LIST")); else account.displayRoomList(); } } /** ISON [nick list] */ void IRC::m_ison(Message message) { string nick; string list; Nick* n; for (size_t i = 0; i < message.countArgs(); i++) { nick = message.getArg(i); if((n = getNick(nick)) && n->isOnline()) { if(!list.empty()) list += " "; list += n->getNickname(); } } user->send(Message(RPL_ISON).setSender(this) .setReceiver(user) .addArg(list)); } /** NAMES chan */ void IRC::m_names(Message message) { Channel* chan = getChannel(message.getArg(0)); if(!chan) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("No such channel")); return; } chan->sendNames(user); } /** TOPIC chan [message] */ void IRC::m_topic(Message message) { Channel* chan = getChannel(message.getArg(0)); if(!chan) user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("No such channel")); else if(message.countArgs() < 2) { string topic = chan->getTopic(); if(topic.empty()) user->send(Message(RPL_NOTOPIC).setSender(this) .setReceiver(user) .addArg(chan->getName()) .addArg("No topic set.")); else user->send(Message(RPL_TOPIC).setSender(this) .setReceiver(user) .addArg(chan->getName()) .addArg(topic)); } else if(!chan->setTopic(user, message.getArg(1))) user->send(Message(ERR_CHANOPRIVSNEEDED).setSender(this) .setReceiver(user) .addArg(chan->getName()) .addArg("You have no rights to change this channel topic!")); } /** INVITE nick chan */ void IRC::m_invite(Message message) { Channel* chan = getChannel(message.getArg(1)); if(!chan) user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(message.getArg(1)) .addArg("No such channel")); else if(chan->invite(user, message.getArg(0), "")) user->send(Message(RPL_INVITING).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg(chan->getName())); } /** KICK chan nick [:reason] */ void IRC::m_kick(Message message) { Channel* chan = getChannel(message.getArg(0)); if(!chan) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("No such channel")); return; } ChanUser* user_chanuser = user->getChanUser(chan); if(!user_chanuser) { user->send(Message(ERR_NOTONCHANNEL).setSender(this) .setReceiver(user) .addArg(chan->getName()) .addArg("You're not on that channel")); return; } ChanUser* chanuser = chan->getChanUser(message.getArg(1)); if(!chanuser) { user->send(Message(ERR_NOSUCHNICK).setSender(this) .setReceiver(user) .addArg(message.getArg(1)) .addArg("No such nick")); return; } chan->kick(user_chanuser, chanuser, message.countArgs() > 2 ? message.getArg(2) : ""); } /** KILL nick [:reason] */ void IRC::m_kill(Message message) { Nick* n = getNick(message.getArg(0)); if(!n) { user->send(Message(ERR_NOSUCHNICK).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("No such nick")); return; } Buddy* buddy = dynamic_cast(n); if(!buddy) { user->send(Message(ERR_NOPRIVILEGES).setSender(this) .setReceiver(user) .addArg("Permission denied: you can only kill a buddy")); return; } RemoteServer* rt = dynamic_cast(buddy->getServer()); if(!rt) { notice(user, buddy->getName() + " is not on a remote server"); return; } string reason = "Removed from buddy list"; if(message.countArgs() > 1 && message.getArg(1).empty() == false) reason += ": " + message.getArg(1); notice(user, "Received KILL message for " + buddy->getNickname() + ": " + reason); buddy->quit("Killed by " + user->getNickname() + " (" + reason + ")"); rt->getAccount().removeBuddy(buddy->getBuddy()); } /** SVSNICK nick new_nick */ void IRC::m_svsnick(Message message) { Nick* n = getNick(message.getArg(0)); Nick* n2 = NULL; if(!n) { user->send(Message(ERR_NOSUCHNICK).setSender(this) .setReceiver(user) .addArg(message.getArg(0)) .addArg("No such nick")); return; } Buddy* buddy = dynamic_cast(n); if(!buddy) { user->send(Message(ERR_NOPRIVILEGES).setSender(this) .setReceiver(user) .addArg("Permission denied: you can only change buddy nickname")); return; } if(!Nick::isValidNickname(message.getArg(1))) { user->send(Message(ERR_ERRONEUSNICKNAME).setSender(this) .setReceiver(user) .addArg("This nick contains invalid characters")); return; } if((n2 = getNick(message.getArg(1))) && n2 != n) { user->send(Message(ERR_NICKNAMEINUSE).setSender(this) .setReceiver(user) .addArg(message.getArg(1)) .addArg("Nickname is already in use")); return; } user->send(Message(MSG_NICK).setSender(buddy) .addArg(message.getArg(1))); renameNick(buddy, message.getArg(1)); buddy->getBuddy().setAlias(message.getArg(1), true); } /** CMD [args ...] */ void IRC::m_cmd(Message message) { string target = message.getArg(0); int ret; string cmd; vector args = message.getArgs(); for(vector::iterator it = args.begin()+1; it != args.end(); ++it) { if(!cmd.empty()) cmd += ' '; cmd += *it; } if(Channel::isChanName(target)) { Channel* c = getChannel(target); if(!c) { user->send(Message(ERR_NOSUCHCHANNEL).setSender(this) .setReceiver(user) .addArg(target) .addArg("No suck channel")); return; } ret = c->sendCommand(cmd); } else { Nick* n = getNick(target); if(!n) { user->send(Message(ERR_NOSUCHNICK).setSender(this) .setReceiver(user) .addArg(target) .addArg("No suck nick")); return; } ret = n->sendCommand(cmd); } switch (ret) { case PURPLE_CMD_STATUS_OK: break; case PURPLE_CMD_STATUS_NOT_FOUND: user->send(Message(ERR_UNKNOWNCOMMAND).setSender(this) .setReceiver(user) .addArg(message.getArg(1)) .addArg("Unknown command")); break; case PURPLE_CMD_STATUS_WRONG_ARGS: user->send(Message(ERR_NEEDMOREPARAMS).setSender(this) .setReceiver(user) .addArg(message.getArg(1)) .addArg("Not enough parameters")); break; case PURPLE_CMD_STATUS_FAILED: user->send(Message(ERR_NEEDMOREPARAMS).setSender(this) .setReceiver(user) .addArg(message.getArg(1)) .addArg("Command failed.")); break; case PURPLE_CMD_STATUS_WRONG_TYPE: user->send(Message(ERR_NOPRIVILEGES).setSender(this) .setReceiver(user) .addArg("Permission Denied: This command doesn't work on this kind of target")); break; case PURPLE_CMD_STATUS_WRONG_PRPL: user->send(Message(ERR_NOPRIVILEGES).setSender(this) .setReceiver(user) .addArg("Permission Denied: That command doesn't work on this protocol.")); break; } } } /* ns irc */ minbif-1.0.5+git20120508/src/server_poll/0000755000175000017500000000000011754151773015624 5ustar sebsebminbif-1.0.5+git20120508/src/server_poll/inetd.cpp0000644000175000017500000000463211754151773017440 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "inetd.h" #include "irc/irc.h" #include "irc/user.h" #include "core/callback.h" #include "core/log.h" #include "core/minbif.h" #include "core/util.h" #include "sockwrap/sockwrap.h" InetdServerPoll::InetdServerPoll(Minbif* application, ConfigSection* config) : ServerPoll(application, config), irc(NULL) { try { irc = new irc::IRC(this, sock::SockWrapper::Builder(getConfig(), fileno(stdin), fileno(stdout)), conf.GetSection("irc")->GetItem("hostname")->String(), conf.GetSection("irc")->GetItem("ping")->Integer()); #ifndef DEBUG /* Don't check if this is a tty, because it always returns false when launched in inetd. */ if(fileno(stderr) >= 0) close(fileno(stderr)); #endif /* DEBUG */ } catch(StrException &e) { b_log[W_ERR] << "Unable to start the IRC daemon: " + e.Reason(); throw ServerPollError(); } } InetdServerPoll::~InetdServerPoll() { delete irc; } void InetdServerPoll::log(size_t level, string msg) const { string cmd = MSG_NOTICE; if(level & W_DEBUG) cmd = MSG_PRIVMSG; for(string line; (line = stringtok(msg, "\n\r")).empty() == false;) irc->getUser()->send(irc::Message(cmd).setSender(irc) .setReceiver(irc->getUser()) .addArg(line)); } void InetdServerPoll::rehash() { if(irc) irc->rehash(); } void InetdServerPoll::kill(irc::IRC* irc) { assert(irc == this->irc); _CallBack* stop_cb = new CallBack(this, &InetdServerPoll::stopServer_cb); g_timeout_add(0, g_callback_delete, stop_cb); } bool InetdServerPoll::stopServer_cb(void*) { delete irc; irc = NULL; getApplication()->quit(); return false; } minbif-1.0.5+git20120508/src/server_poll/inetd.h0000644000175000017500000000222011754151773017074 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SERVER_POLL_INETD_H #define SERVER_POLL_INETD_H #include "poll.h" namespace irc { class IRC; }; class InetdServerPoll : public ServerPoll { irc::IRC* irc; public: InetdServerPoll(Minbif* application, ConfigSection* _config); ~InetdServerPoll(); void rehash(); void kill(irc::IRC* irc); bool stopServer_cb(void*); void log(size_t level, string log) const; }; #endif /* SERVER_POLL_INETD_H */ minbif-1.0.5+git20120508/src/server_poll/poll.cpp0000644000175000017500000000271411754151773017302 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "poll.h" #include "inetd.h" #include "daemon_fork.h" #include "core/log.h" ServerPoll* ServerPoll::build(ServerPoll::poll_type_t type, Minbif* application) { ConfigSection* config; switch(type) { case ServerPoll::INETD: config = conf.GetSection("irc")->GetSection("inetd"); return new InetdServerPoll(application, config); case ServerPoll::DAEMON_FORK: config = conf.GetSection("irc")->GetSection("daemon"); return new DaemonForkServerPoll(application, config); case ServerPoll::DAEMON: default: b_log[W_ERR] << "Type " << type << " is not implemented yet."; } throw ServerPollError(); } ServerPoll::ServerPoll(Minbif* _app, ConfigSection* _config) : application(_app), config(_config) {} minbif-1.0.5+git20120508/src/server_poll/daemon_fork.cpp0000644000175000017500000002712711754151773020625 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include "daemon_fork.h" #include "irc/irc.h" #include "irc/user.h" #include "irc/message.h" #include "irc/replies.h" #include "core/callback.h" #include "core/log.h" #include "core/minbif.h" #include "core/util.h" #include "sockwrap/sock.h" #include "sockwrap/sockwrap.h" DaemonForkServerPoll::DaemonForkServerPoll(Minbif* application, ConfigSection* config) : ServerPoll(application, config), irc(NULL), sock(-1), read_cb(NULL) { ConfigSection* section = getConfig(); if(section->Found() == false) { b_log[W_ERR] << "Missing section irc/daemon"; throw ServerPollError(); } maxcon = section->GetItem("maxcon")->Integer(); if(section->GetItem("background")->Boolean()) { int r = fork(); if(r < 0) { b_log[W_ERR] << "Unable to start in background: " << strerror(errno); throw ServerPollError(); } else if(r > 0) exit(EXIT_SUCCESS); /* parent exits. */ setsid(); for (r=getdtablesize();r>=0;--r) close(r); /* Redirect the default streams to /dev/null. * We hope that the descriptors will be 0, 1 and 2 */ r=open("/dev/null",O_RDWR); /* open stdin */ (void)dup(r); /* stdout */ (void)dup(r); /* stderr */ umask(027); string path = conf.GetSection("path")->GetItem("users")->String(); if (chdir(path.c_str()) < 0) { b_log[W_ERR] << "Unable to change directory: " << strerror(errno); throw ServerPollError(); } } struct addrinfo *addrinfo_bind, *res, hints; string bind_addr = section->GetItem("bind")->String(); uint16_t port = (uint16_t)section->GetItem("port")->Integer(); unsigned int reuse_addr = 1; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if(getaddrinfo(bind_addr.c_str(), t2s(port).c_str(), &hints, &addrinfo_bind)) { b_log[W_ERR] << "Could not parse address " << bind_addr << ":" << port; throw ServerPollError(); } for(res = addrinfo_bind; res && sock < 0; res = res->ai_next) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if(sock < 0) continue; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof reuse_addr); if(bind(sock, res->ai_addr, res->ai_addrlen) < 0 || listen(sock, 5) < 0) { close(sock); sock = -1; } } if(sock < 0) b_log[W_ERR] << "Unable to listen on " << bind_addr << ":" << port << ": " << strerror(errno); else { read_cb = new CallBack(this, &DaemonForkServerPoll::new_client_cb); read_id = glib_input_add(sock, (PurpleInputCondition)PURPLE_INPUT_READ, g_callback_input, read_cb); } freeaddrinfo(addrinfo_bind); if(!read_cb) throw ServerPollError(); } DaemonForkServerPoll::~DaemonForkServerPoll() { if(read_id >= 0) g_source_remove(read_id); delete read_cb; if(sock >= 0) close(sock); delete irc; for(vector::iterator it = childs.begin(); it != childs.end(); ++it) { close((*it)->fd); delete (*it)->read_cb; g_source_remove((*it)->read_id); delete *it; } } bool DaemonForkServerPoll::new_client_cb(void*) { struct sockaddr_in newcon; socklen_t addrlen = sizeof newcon; int new_socket = accept(sock, (struct sockaddr *) &newcon, &addrlen); if(new_socket < 0) { b_log[W_WARNING] << "Could not accept new connection: " << strerror(errno); return true; } if(maxcon > 0 && childs.size() >= (unsigned)maxcon) { static const char error[] = "ERROR :Closing Link: Too much connections on server\r\n"; send(new_socket, error, sizeof(error), 0); close(new_socket); return true; } int fds[2]; if(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) { b_log[W_WARNING] << "Unable to create IPC socket for client: " << strerror(errno); fds[0] = fds[1] = -1; } else { sock_make_nonblocking(fds[0]); sock_make_nonblocking(fds[1]); } pid_t client_pid = fork(); if(client_pid < 0) { b_log[W_ERR] << "Unable to fork while receiving a new connection: " << strerror(errno); close(new_socket); return true; } else if(client_pid > 0) { /* Parent */ b_log[W_INFO] << "Creating new process with pid " << client_pid; close(new_socket); if(fds[0] >= 0) { child_t* child = new child_t(); child->fd = fds[0]; child->read_cb = new CallBack(this, &DaemonForkServerPoll::ipc_read, child); child->read_id = glib_input_add(child->fd, (PurpleInputCondition)PURPLE_INPUT_READ, g_callback_input, child->read_cb); childs.push_back(child); close(fds[1]); } } else { /* Child */ close(sock); sock = -1; g_source_remove(read_id); read_id = -1; delete read_cb; read_cb = NULL; if(fds[1] >= 0) { sock = fds[1]; read_cb = new CallBack(this, &DaemonForkServerPoll::ipc_read); read_id = glib_input_add(sock, (PurpleInputCondition)PURPLE_INPUT_READ, g_callback_input, read_cb); close(fds[0]); /* Cleanup all childs accumulated when I was parent. */ for(vector::iterator it = childs.begin(); it != childs.end(); it = childs.erase(it)) { child_t* child = *it; close(child->fd); delete child->read_cb; g_source_remove(child->read_id); delete child; } } try { irc = new irc::IRC(this, sock::SockWrapper::Builder(getConfig(), new_socket, new_socket), conf.GetSection("irc")->GetItem("hostname")->String(), conf.GetSection("irc")->GetItem("ping")->Integer()); } catch(StrException &e) { b_log[W_ERR] << "Unable to start the IRC daemon: " + e.Reason(); getApplication()->quit(); } } return true; } DaemonForkServerPoll::ipc_cmds_t DaemonForkServerPoll::ipc_cmds[] = { { MSG_WALLOPS, &DaemonForkServerPoll::m_wallops, 2 }, { MSG_REHASH, &DaemonForkServerPoll::m_rehash, 0 }, { MSG_DIE, &DaemonForkServerPoll::m_die, 2 }, { MSG_OPER, &DaemonForkServerPoll::m_oper, 1 }, { MSG_USER, &DaemonForkServerPoll::m_user, 1 }, }; /** OPER nick * * A user on a minbif instance is now an IRC Operator */ void DaemonForkServerPoll::m_oper(child_t* child, irc::Message m) { if(child) ipc_master_broadcast(m, child); b_log[W_SNO|W_INFO] << m.getArg(0) << " is now an IRC Operator!"; } /** WALLOPS nick message * * Send a message to every minbif instances. */ void DaemonForkServerPoll::m_wallops(child_t* child, irc::Message m) { if(child) ipc_master_broadcast(m); else if(irc) irc->getUser()->send(irc::Message(MSG_WALLOPS).setSender(m.getArg(0)) .addArg(m.getArg(1))); } /** REHASH * * Reload configuration. */ void DaemonForkServerPoll::m_rehash(child_t* child, irc::Message m) { rehash(); } /** DIE * * Close server. */ void DaemonForkServerPoll::m_die(child_t* child, irc::Message m) { if(child) ipc_master_broadcast(m); b_log[W_SNO|W_INFO] << "Shutdown requested by " << m.getArg(0) << ": " << m.getArg(1); if(irc) irc->quit("Shutdown requested by " + m.getArg(0)); else getApplication()->quit(); } /** USER nick * * New minbif instance tells his username. */ void DaemonForkServerPoll::m_user(child_t* child, irc::Message m) { if (child) { child->username = m.getArg(0); /* Disconnect any other minbif instance logged on the same user. */ for(vector::iterator it = childs.begin(); it != childs.end(); ++it) if (*it != child && !strcasecmp((*it)->username.c_str(), child->username.c_str())) ipc_master_send(*it, irc::Message(MSG_DIE).addArg(child->username) .addArg("You are logged from another location.")); } } bool DaemonForkServerPoll::ipc_read(void* data) { child_t* child = NULL; if(data) child = static_cast(data); static char buf[512]; ssize_t r; int fd; if(child) fd = child->fd; else fd = sock; if((r = recv(fd, buf, sizeof buf - 1, MSG_PEEK)) <= 0) { if(r < 0 && sockerr_again()) return true; if(child) { b_log[W_INFO] << "IPC: a child left: " << strerror(errno); for(vector::iterator it = childs.begin(); it != childs.end();) if(child->fd == (*it)->fd) it = childs.erase(it); else ++it; close(child->fd); g_source_remove(child->read_id); delete child->read_cb; delete child; } else { b_log[W_INFO|W_SNO] << "IPC: master left: " << strerror(errno); g_source_remove(read_id); read_id = -1; delete read_cb; read_cb = NULL; close(sock); sock = -1; } return false; } buf[r] = 0; char* eol; if(!(eol = strstr(buf, "\r\n"))) return true; else r = eol - buf + 2; if(recv(fd, buf, r, 0) != r) return false; buf[r - 2] = 0; irc::Message m = irc::Message::parse(buf); unsigned i = 0; for(; i < (sizeof ipc_cmds / sizeof *ipc_cmds) && m.getCommand() != ipc_cmds[i].cmd; ++i) ; if(i >= (sizeof ipc_cmds / sizeof *ipc_cmds)) { b_log[W_WARNING] << "Received unknown command from IPC: " << buf; return true; } if(m.countArgs() < ipc_cmds[i].min_args) { b_log[W_WARNING] << "Received malformated command from IPC: " << buf; return true; } (this->*ipc_cmds[i].func)(child, m); return true; } bool DaemonForkServerPoll::ipc_master_send(child_t* child, const irc::Message& m) { if(!child) return false; string msg = m.format(); if(write(child->fd, msg.c_str(), msg.size()) <= 0) { b_log[W_ERR] << "Error while sending: " << strerror(errno); return false; } return true; } bool DaemonForkServerPoll::ipc_master_broadcast(const irc::Message& m, child_t* butone) { bool ret = false; for(vector::iterator it = childs.begin(); it != childs.end(); ++it) if(butone != *it && ipc_master_send(*it, m)) ret = true; return ret; } bool DaemonForkServerPoll::ipc_child_send(const irc::Message& m) { if(sock < 0) return false; string msg = m.format(); if(write(sock, msg.c_str(), msg.size()) <= 0) { b_log[W_ERR] << "Error while sending: " << strerror(errno); return false; } return true; } bool DaemonForkServerPoll::ipc_send(const irc::Message& m) { if(irc) return ipc_child_send(m); else return ipc_master_broadcast(m); } void DaemonForkServerPoll::log(size_t level, string msg) const { string cmd = MSG_NOTICE; if(level & W_DEBUG) cmd = MSG_PRIVMSG; if(irc) for(string line; (line = stringtok(msg, "\n\r")).empty() == false;) irc->getUser()->send(irc::Message(cmd).setSender(irc) .setReceiver(irc->getUser()) .addArg(line)); else if(!(level & W_SNO)) std::cout << msg << std::endl; } void DaemonForkServerPoll::rehash() { if(irc) irc->rehash(); else ipc_master_broadcast(irc::Message(MSG_REHASH)); } void DaemonForkServerPoll::kill(irc::IRC* irc) { assert(!irc || irc == this->irc); _CallBack* stop_cb = new CallBack(this, &DaemonForkServerPoll::stopServer_cb); g_timeout_add(0, g_callback_delete, stop_cb); } bool DaemonForkServerPoll::stopServer_cb(void*) { delete irc; irc = NULL; getApplication()->quit(); return false; } minbif-1.0.5+git20120508/src/server_poll/poll.h0000644000175000017500000000313211754151773016742 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SERVER_POLL_H #define SERVER_POLL_H #include #include #include "core/config.h" using std::string; class Minbif; namespace irc { class IRC; class Message; }; class ServerPollError : public std::exception {}; class ServerPoll { Minbif* application; ConfigSection* config; protected: Minbif* getApplication() const { return application; } ConfigSection* getConfig() const { return config; } public: enum poll_type_t { INETD, DAEMON, DAEMON_FORK }; static ServerPoll* build(poll_type_t type, Minbif* application); ServerPoll(Minbif* application, ConfigSection* _config); virtual ~ServerPoll() {} virtual void kill(irc::IRC* irc) = 0; virtual void rehash() = 0; virtual bool ipc_send(const irc::Message& m) { return false; } virtual void log(size_t level, string string) const = 0; }; #endif /* SERVER_POLL_H */ minbif-1.0.5+git20120508/src/server_poll/daemon_fork.h0000644000175000017500000000654011754151773020266 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SERVER_POLL_DAEMON_FORK_H #define SERVER_POLL_DAEMON_FORK_H #include #include "poll.h" namespace irc { class IRC; class Message; }; class _CallBack; using std::vector; class DaemonForkServerPoll : public ServerPoll { /** IPC child data structure */ struct child_t { int fd; int read_id; _CallBack* read_cb; string username; }; /** IPC commands array. */ static struct ipc_cmds_t { const char* cmd; void (DaemonForkServerPoll::*func) (child_t* child, irc::Message m); unsigned min_args; } ipc_cmds[]; /** \page IPC * * Daemon fork mode forks everytimes there is a new connection. * * Communication between children and parent is made with two sockets * that are shared. Every commands are formatted like an IRC command, * and the irc::Message class can be used to parse or format commands. * Note that it is forbidden to set a sender or a receiver. * */ void m_wallops(child_t* child, irc::Message m); /**< IPC handler for the WALLOPS command. */ void m_rehash(child_t* child, irc::Message m); /**< IPC handler for the REHASH command. */ void m_die(child_t* child, irc::Message m); /**< IPC handler for the DIE command. */ void m_oper(child_t* child, irc::Message m); /**< IPC handler for the OPER command. */ void m_user(child_t* child, irc::Message m); /**< IPC handler for the USER command. */ irc::IRC* irc; int maxcon; int sock; int read_id; _CallBack *read_cb; vector childs; bool ipc_read(void*); /** Master sends a IPC message to a child. * * @param child child data structure * @param m message to send * @return true if the message has correctly been sent. */ bool ipc_master_send(child_t* child, const irc::Message& m); /** Master broadcasts a IPC message to every children. * * @param m message to send * @param butone optional argument. If != NULL, the message is sent * to everybody except this one * @return true if the message has correctly been sent to at least * one child */ bool ipc_master_broadcast(const irc::Message& m, child_t* butone = NULL); /** Child send a message to his master. * * @param m message to send * @return true if the message has correctly been sent. */ bool ipc_child_send(const irc::Message& m); public: DaemonForkServerPoll(Minbif* application, ConfigSection* _config); ~DaemonForkServerPoll(); bool new_client_cb(void*); void rehash(); void kill(irc::IRC* irc); bool stopServer_cb(void*); bool ipc_send(const irc::Message& msg); void log(size_t level, string log) const; }; #endif /* SERVER_POLL_DAEMON_FORK_H */ minbif-1.0.5+git20120508/doc/0000755000175000017500000000000011754151773013246 5ustar sebsebminbif-1.0.5+git20120508/doc/Doxyfile0000644000175000017500000017566511754151773015000 0ustar sebseb# Doxyfile 1.5.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = "Minbif" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 2.0 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = . # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, # Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = YES # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = YES # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it parses. # With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this tag. # The format is ext=language, where ext is a file extension, and language is one of # the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, # Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat # .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = YES # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = NO # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by # doxygen. The layout file controls the global structure of the generated output files # in an output format independent way. The create the layout file that represents # doxygen's defaults, run doxygen with the -l option. You can optionally specify a # file name after the option, if omitted DoxygenLayout.xml will be used as the name # of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = ../src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.d \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.idl \ *.odl \ *.cs \ *.php \ *.php3 \ *.inc \ *.m \ *.mm \ *.dox \ *.C \ *.CC \ *.C++ \ *.II \ *.I++ \ *.H \ *.HH \ *.H++ \ *.CS \ *.PHP \ *.PHP3 \ *.M \ *.MM # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = YES #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER # are set, an additional index file will be generated that can be used as input for # Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated # HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. # For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's # filter section matches. # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = YES # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = NO #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 1000 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Options related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO minbif-1.0.5+git20120508/doc/minbif.xinetd0000644000175000017500000000126311754151773015731 0ustar sebseb# xinetd file for minbif. Please check this file before using it, the # user, port and/or binary location might be wrong. # This file assumes you have ircd somewhere in your /etc/services, if things # don't work, check that file first. # You also should set type=0 in minbif.conf service ircd { socket_type = stream protocol = tcp wait = no # You might want to change these two user = minbif server = /usr/bin/minbif server_args = /etc/minbif/minbif.conf # uncomment this to change the port and avoid editing /etc/services #type = UNLISTED port = 6667 disable = no } minbif-1.0.5+git20120508/tests/0000755000175000017500000000000011754151773013643 5ustar sebsebminbif-1.0.5+git20120508/tests/test_buddylist.py0000644000175000017500000000617011754151773017263 0ustar sebseb# -*- coding: utf-8 -*- """ Minbif - IRC instant messaging gateway Copyright(C) 2009 Romain Bignon This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ import sys import re from test import Test, Instance class TestBuddyList(Test): NAME = 'buddy_list' INSTANCES = {'minbif1': Instance(), 'minbif2': Instance()} TESTS = ['init', ]#'addbuddy', 'renamebuddy', 'delbuddy'] def test_init(self): if not self['minbif1'].create_account('jabber', channel='&minbif'): return False if not self['minbif1'].wait_connected('jabber'): return False if not self['minbif1'].clean_buddies(): return False if not self['minbif2'].create_account('jabber', channel='&minbif'): return False if not self['minbif2'].wait_connected('jabber'): return False if not self['minbif2'].clean_buddies(): return False return True def test_addbuddy(self): self['minbif1'].request_answer('New request:', 'authorize', 1) acc1name, acc1 = self['minbif1'].get_accounts().popitem() self['minbif2'].write('INVITE %s:jabber &minbif' % acc1.username) self['minbif1'].request_answer('New request:', 'authorize', 5) self['minbif2'].log('Wait for join') while 1: msg = self['minbif2'].readmsg('JOIN', 4) if not msg: return False m = re.match('([^!]*)!([^@]*)@(.*)', msg.sender) if m: return m.group(2) == acc1.username.split('@')[0] def test_renamebuddy(self): buddies = self['minbif2'].get_buddies() if len(buddies) < 1: return False nick, buddy = buddies.popitem() self['minbif2'].write('SVSNICK %s cacaprout' % nick) while 1: msg = self['minbif2'].readmsg('NICK', 2) if not msg: return False if msg.sender.startswith('%s!' % nick) and msg.receiver == 'cacaprout': return True def test_delbuddy(self): buddies = self['minbif2'].get_buddies() if len(buddies) < 1: self['minbif2'].log('Assert failed "len(buddies) == 1"') return False nick, buddy = buddies.popitem() self['minbif2'].write('KILL %s' % nick) self['minbif2'].log("Wait for quit") while 1: msg = self['minbif2'].readmsg('QUIT', 2) if not msg: return False return msg.sender.startswith('%s!' % nick) if __name__ == '__main__': test = TestBuddyList() if test.run(): sys.exit(0) else: sys.exit(1) minbif-1.0.5+git20120508/tests/nobuffer.c0000644000175000017500000000046111754151773015616 0ustar sebseb/* * Compile with : * gcc -shared -o nobuffer.so interceptor.c */ #include #if defined(__GNUC__) # define CONSTRUCTOR __attribute__((constructor)) #else # define CONSTRUCTOR #endif CONSTRUCTOR void init() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } minbif-1.0.5+git20120508/tests/structs.py0000644000175000017500000000233311754151773015725 0ustar sebseb# -*- coding: utf-8 -*- """ Minbif - IRC instant messaging gateway Copyright(C) 2009 Romain Bignon This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ class Account: def __init__(self, proto='', username='', password='', options={}): self.proto = proto self.username = username self.password = password self.options = options self.state = '' class Buddy: def __init__(self, servername, nickname='', username='', hostname='', realname=''): self.servername = servername self.nickname = nickname self.username = username self.hostname = hostname self.realname = realname minbif-1.0.5+git20120508/tests/config.py.example0000644000175000017500000000106511754151773017116 0ustar sebseb# How to run the automated tests # ============================== # # 1) run "make tests" # 2) Rename this file to config.py and replaces values # # You need at least two xmpp (jabber) accounts to run the test. # # WARNING! Don't use current usage accounts: these accounts' roster # will be blindly erased and fill by test datas. # # YOU WAS WARNED! from structs import Account ACCOUNTS = [Account('jabber', 'blah@jabber.fr', 'mypasswd'), Account('jabber', 'counter@jabber.de', 'otherpasswd'), Account('msn', 'blah@hotmail.com', 'pppp')] minbif-1.0.5+git20120508/tests/test_accounts.py0000644000175000017500000000452211754151773017076 0ustar sebseb#!/bin/python # -*- coding: utf-8 -*- """ Minbif - IRC instant messaging gateway Copyright(C) 2009 Romain Bignon This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ import sys import re from test import Test, Instance class TestAccounts(Test): NAME = 'accounts' INSTANCES = {'minbif1': Instance()} TESTS = ['addaccount', 'connected', 'editaccount', 'disconnect', 'removeaccount'] def test_addaccount(self): if not self['minbif1'].create_account('jabber', channel='&minbif'): return False accounts = self['minbif1'].get_accounts() if len(accounts) == 1: acc = accounts.popitem()[1] return acc.proto == 'jabber' return False def test_connected(self): return self['minbif1'].wait_connected('jabber') def test_editaccount(self): # flush while self['minbif1'].readline(): pass acc = self['minbif1'].get_full_account('jabber') require_tls = acc.options['require_tls'] self['minbif1'].write("MAP edit jabber require_tls %s" % (require_tls == "true" and "false" or "true")) self['minbif1'].readmsg('NOTICE', 1) acc = self['minbif1'].get_full_account('jabber') return acc.options['require_tls'] == (require_tls == "true" and "false" or "true") def test_disconnect(self): self['minbif1'].write('SQUIT jabber') accounts = self['minbif1'].get_accounts() return accounts.popitem()[1].state == 'disconnected' def test_removeaccount(self): self['minbif1'].remove_account('jabber') accounts = self['minbif1'].get_accounts() return len(accounts) == 0 if __name__ == '__main__': test = TestAccounts() if test.run(): sys.exit(0) else: sys.exit(1) minbif-1.0.5+git20120508/tests/run_tests.sh0000755000175000017500000000013311754151773016225 0ustar sebseb#!/bin/bash for t in $(find . -name "test_*.py" | sort) do python "$t" || exit 1 done minbif-1.0.5+git20120508/tests/test.py0000644000175000017500000003430711754151773015203 0ustar sebseb# -*- coding: utf-8 -*- """ Minbif - IRC instant messaging gateway Copyright(C) 2009 Romain Bignon This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ from __future__ import with_statement import sys import os import traceback from select import select from subprocess import Popen, PIPE, STDOUT from time import sleep, time import re try: import config except ImportError: print >>sys.stderr, 'Error: please rename config.py.example to config.py and edit it!' sys.exit(1) from structs import Account, Buddy NOBUFFER_PATH = os.path.normpath(os.path.join(os.path.dirname(__file__), 'libnobuffer.so')) MINBIF_PATH = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'build', 'src', 'minbif')) def getBacktrace(empty="Empty backtrace."): """ Try to get backtrace as string. Returns "Error while trying to get backtrace" on failure. """ try: info = sys.exc_info() trace = traceback.format_exception(*info) sys.exc_clear() if trace[0] != "None\n": return "".join(trace) except: # No i18n here (imagine if i18n function calls error...) return "Error while trying to get backtrace" return empty class Test: NAME = '' INSTANCES = {} TESTS = [] PATH = '/tmp/minbif-tests' def __getitem__(self, inst): return self.INSTANCES[inst] def run(self, stop_on_failure=True): print '\nStarting test: %s' % self.NAME ret = self._run(stop_on_failure) for name, instance in self.INSTANCES.iteritems(): instance.stop() if ret: print 'End of test %s: success' % self.NAME else: print 'End of test %s: failed' % self.NAME self.display_logs() print '' return ret def _run(self, stop_on_failure=True): if not self.rm_and_mkdir(self.PATH): return False for name, instance in self.INSTANCES.iteritems(): sys.stdout.write('\tLaunch %-25s' % (name + ':')) inst_path = '%s/%s' % (self.PATH, name) if not self.rm_and_mkdir(inst_path): return False if instance.start(inst_path): print '[Success]' else: print '[Failed]' return False for test in self.TESTS: if not self.run_test(test) and stop_on_failure: return False return True def run_test(self, test): sys.stdout.write('\tTest %-26s ' % (test + ':')) if not hasattr(self, 'test_%s' % test): print '[Not found]' else: func = getattr(self, 'test_%s' % test) msg = '' try: ret = func() except Exception, e: ret = False msg = '%s: %s' % (type(e).__name__, str(e)) for instance in self.INSTANCES.itervalues(): instance.log(getBacktrace()) if ret: print '[Success]' return True else: print '[Failed] %s' % msg return False def rm_and_mkdir(self, path): try: for root, dirs, files in os.walk(path, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) except OSError, e: print 'Error: unable to remove directory %s: %s' % (path, e) return False try: os.rmdir(path) except: pass try: os.mkdir(path) except OSError, e: print 'Error: unable to create directory %s: %s' % (path, e) return False return True def display_logs(self): for name, instance in self.INSTANCES.iteritems(): print '\nLog for %s:' % name instance.display_logs() class Message: def __init__(self, cmd, sender=None, receiver=None, args=[]): self.cmd = cmd self.sender = sender self.receiver = receiver self.args = args @staticmethod def parseline(line): args = line.split() if not args or len(args) < 3: return None cmd = None sender = None receiver = None if args[0][0] == ':': sender = args.pop(0)[1:] cmd = args.pop(0) receiver = args.pop(0) for i in xrange(len(args)): if args[i][0] == ':': args[i] = ' '.join(args[i:])[1:] args = args[:i+1] break return Message(cmd, sender, receiver, args) class Instance: DEFAULT_CONF = {'path': {'users': ''}, 'irc': {'hostname': 'im.symlink.me', 'type': 0, 'ping': 0, }, 'file_transfers': {'enabled': 'true', 'dcc': 'true', 'port_range': '1024-65535', }, 'aaa': {}, 'logging': {'level': 'DESYNCH WARNING ERR INFO DEBUG', 'to_syslog': 'false' }, } def __init__(self, config={}): self.config = config self.path = '' self.logs = [] self.process = None def display_logs(self): for log in self.logs: print ' %s' % log def log(self, s): self.logs.append('%.3f %s' % (time(), s)) def stop(self): if not self.process: return try: while self.readline(): pass self.write("QUIT") except IOError: pass else: self.process.wait() self.process = None def write(self, msg): if self.process: self.log("> %s" % msg) self.process.stdin.write("%s\n" % msg) def readline(self, timeout=0): out = self.process.stdout if timeout is not None: ready = select([out.fileno()], [], [], timeout)[0] if not ready: return None line = out.readline() if not line: return None line = line.rstrip() self.log("< %s" % line) return line def readmsg(self, cmd=None, timeout=0): start = time() while start + timeout >= (1 if timeout == 0 else time()): line = self.readline(timeout) if not line: return None msg = Message.parseline(line) if not msg: print line continue if not cmd or isinstance(cmd, (str,unicode)) and msg.cmd == cmd \ or msg.cmd in cmd: return msg def start(self, path): try: self.path = path config_path = '%s/minbif.conf' % path self.write_config(config_path) self.process = Popen((MINBIF_PATH, config_path), stdin=PIPE, stdout=PIPE, stderr=STDOUT, env={"LD_PRELOAD": NOBUFFER_PATH}) return self.login() except Exception, e: self.process = None self.log(getBacktrace()) sys.stdout.write("(%s) " % e) return False def update_config(self, config, config2): for key, value in config2.iteritems(): if key in config and isinstance(config[key], dict): self.update_config(config[key], value) else: config[key] = value def write_config(self, filename): config = self.DEFAULT_CONF.copy() config['path']['users'] = '%s/users' % self.path self.update_config(config, self.config) self.config = config with open(filename, 'w') as f: self.write_config_section(f, 0, config) def write_config_section(self, fp, depth, section): tabs = ' ' * depth for key, value in section.iteritems(): if isinstance(value, dict): fp.write("%s%s {\n" % (tabs, key)) self.write_config_section(fp, depth+1, value) fp.write("%s}\n" % tabs) else: fp.write("%s%s = %s\n" % (tabs, key, value)) def login(self, nickname="minbif", password="minbifrocks"): self.write("USER minbif * * :MinBif") self.write("PASS %s" % password) self.write("NICK %s" % nickname) msg = self.readmsg("001", 5) return (msg != None) def create_account(self, proto=None, channel='&minbif'): account = None if not proto: try: account = config.ACCOUNTS.pop() except IndexError: return False else: for acc in xrange(len(config.ACCOUNTS)): if config.ACCOUNTS[acc].proto == proto: account = config.ACCOUNTS.pop(acc) break if not account: return False options = '' for key, value in account.options: options += ' ' if isinstance(value, bool): if value: options += "-%s" % key else: options += "-!%s" % key else: options += "-%s \"%s\"" % (key, value) self.write("MAP add %s %s %s %s %s" % (account.proto, account.username, account.password, options, channel)) return self.readmsg("017", 1) != None def remove_account(self, name): self.write("MAP delete %s" % name) return self.readmsg("017", 1) != None def wait_connected(self, name): accounts = self.get_accounts() if not name in accounts.iterkeys(): return False acc = accounts[name] if acc.state == 'connected': return True if acc.state != 'connecting': return False while 1: msg = self.readmsg(('NOTICE','PRIVMSG'),5) if not msg: return False m = re.match('\*\*\* Notice -- Connection to ([^ ]+):%s established!' % name, msg.args[0]) if m: return True if msg.sender.startswith('request!') and msg.args[0] == 'New request: SSL Certificate Verification': self.write("PRIVMSG request :accept") def get_accounts(self): # flush while self.readline(): pass self.write("MAP") accounts = {} while 1: msg = self.readmsg(("015", "017"), 2) if not msg: return False if msg.cmd == "017": break line = msg.args[0] # me if not line.startswith('|') and not line.startswith('`'): continue m = re.match(".([- ][\*\+]*)(.+):([[a-zA-Z]+)([0-9]*)", line) if m: acc = Account(proto=m.group(3), username=m.group(2)) prompt2state = {'-': 'connected', ' ': 'disconnected', '-*': 'connecting', '-+': 'added' } acc.state = prompt2state[m.group(1)] accounts['%s%s' % (m.group(3), m.group(4))] = acc return accounts def get_full_account(self, accname): while self.readline(): pass self.write("MAP edit %s" % accname) acc = None while 1: msg = self.readmsg("NOTICE", 1) if not msg: return acc m = re.match("-- Parameters of account (.+):([a-zA-Z]+)([0-9]*) --", msg.args[0]) if m: acc = Account(proto=m.group(2), username=m.group(1)) else: m = re.match("([^ ]+) = (.*)", msg.args[0]) if m: acc.options[m.group(1)] = m.group(2) def get_buddies(self, accname=''): while self.readline(): pass self.write("WHO %s" % accname) buddies = {} while 1: msg = self.readmsg(('352', '315'), 3) if not msg or msg.cmd == '315': return buddies servername = msg.args[3] if servername == self.config['irc']['hostname']: continue nickname = msg.args[4] username = msg.args[1] hostname = msg.args[2] realname = msg.args[6][2:] buddy = Buddy(servername, nickname, username, hostname, realname) buddies[nickname] = buddy def request_answer(self, question, answer, timeout=1): self.log('Wait request "%s"' % question) while 1: msg = self.readmsg('PRIVMSG', timeout) if not msg: return False if msg.sender.startswith('request!') and msg.args[0].startswith(question): self.write('PRIVMSG request :%s' % answer) return True def clean_buddies(self, accname=''): self.request_answer('New request: Authorize buddy?', 'authorize', 0) while 1: buddies = self.get_buddies(accname) if not buddies: break for nick, buddy in buddies.iteritems(): self.write("KILL %s" % nick) self.request_answer('New request: Authorize buddy?', 'authorize', 0) return True minbif-1.0.5+git20120508/tests/Makefile0000644000175000017500000000021711754151773015303 0ustar sebseblibnobuffer.so: nobuffer.c gcc -o $@ -shared nobuffer.c $(CFLAGS) -fPIC all: libnobuffer.so clean: rm -f libnobuffer.so .PHONY: all clean minbif-1.0.5+git20120508/valgrind.sh0000755000175000017500000000027611754151773014653 0ustar sebseb#!/bin/sh APP=$* LOG=$1-valgrind.log valgrind \ --verbose \ --log-file=$LOG \ --leak-check=full \ --run-libc-freeres=no \ --suppressions=valgrind-suppressions \ $APP minbif-1.0.5+git20120508/COPYING0000644000175000017500000004302111754151773013534 0ustar sebseb GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. minbif-1.0.5+git20120508/Makefile0000644000175000017500000000072711754151773014147 0ustar sebsebinclude Makefile.options.defaults -include Makefile.options.local include Makefile.parser CMAKE_OPTIONS = $(EXTRA_CMAKE_FLAGS) $(CMAKE_PREFIX) all: build/Makefile $(MAKE) -C build all build/Makefile: @[ -d build ] || mkdir build cd build && cmake .. $(CMAKE_OPTIONS) || cd .. && rm -rf build install: $(MAKE) -C build install clean: rm -rf build rm -rf release doc: cd doc/ && /usr/bin/doxygen tests: $(MAKE) -C tests .PHONY: all clean install doc tests minbif-1.0.5+git20120508/AUTHORS0000644000175000017500000000310211754151773013545 0ustar sebsebMinbif is a free software written by: Romain Bignon * Lead developer; * Developer of the prpl-coincoin plugin. Marc Dequènes (Duck) * PAM authentication; * TLS support; * Developer of the prpl-gayattitude plugin. Sebastien Delafond * Maintainer of the Debian package; * irssi scripts. Josh Marchán * Fixes related to the IRC protocol. William Lallemand * Patch related to PAM. Julien Miotte * Various patches on the IRC protocol. Laurent Bachelier * Patches on cmake and configuration files. Justin Wheeler * irssi scripts. CissWit * weechat scripts. varogami * weechat script to rename Facebook buddies. Matias Russitto * irssi Google+ renamer script. Frank Steinborn * Maintainer of the FreeBSD port. Francois Lallart * Parameters management with /WHO. Brandon Williams * Patch on /JOIN command. Thomas Lecavelier * Ass-kisser on DLFP; * Use of imlib to load sent buddy icons. Michael Scherer * Patch on cmake files. Landry Breuil * Maintainer of the OpenBSD port. Discover how to contribute to this great software: http://minbif.im/How_to_contribute minbif-1.0.5+git20120508/plugins/0000755000175000017500000000000011754151773014162 5ustar sebsebminbif-1.0.5+git20120508/plugins/gayattitude/0000755000175000017500000000000011754151773016506 5ustar sebsebminbif-1.0.5+git20120508/plugins/gayattitude/ga_buddylist.h0000644000175000017500000000035311754151773021332 0ustar sebseb#ifndef GA_GABUDDYLIST_H #define GA_GABUDDYLIST_H #include #include "ga_account.h" void ga_buddylist_update(GayAttitudeAccount* gaa); void ga_buddylist_check_status(GayAttitudeAccount* gaa); #endif /* GA_GABUDDYLIST_H */ minbif-1.0.5+git20120508/plugins/gayattitude/ga_message.h0000644000175000017500000000205111754151773020750 0ustar sebseb#ifndef GA_GAMESSAGE_H #define GA_GAMESSAGE_H #include #include "ga_account.h" #include "ga_buddy.h" typedef struct _GayAttitudeDelayedMessageRequest GayAttitudeDelayedMessageRequest; typedef struct _GayAttitudeConversationInfo GayAttitudeConversationInfo; typedef struct _GayAttitudeMessageExtraInfo GayAttitudeMessageExtraInfo; struct _GayAttitudeDelayedMessageRequest { GayAttitudeAccount *gaa; GayAttitudeBuddy *gabuddy; gchar *what; PurpleMessageFlags flags; }; struct _GayAttitudeConversationInfo { PurpleConversation *conv; GayAttitudeBuddy *gabuddy; guint64 latest_msg_id; gchar *url_path, *checksum; time_t timestamp; gboolean replied; }; struct _GayAttitudeMessageExtraInfo { guint64 id; gchar *sender, *conv_name, *msg; }; int ga_message_send(GayAttitudeAccount *gaa, GayAttitudeBuddy *gabuddy, const char *what, PurpleMessageFlags flags); void ga_message_check_received(GayAttitudeAccount *gaa); void ga_message_close_conversation(GayAttitudeAccount *gaa, const gchar *conv_name); #endif /* GA_GAMESSAGE_H */ minbif-1.0.5+git20120508/plugins/gayattitude/ga_parsing.c0000644000175000017500000000164111754151773020766 0ustar sebseb #include "ga_parsing.h" /* if attrib == NULL, then get text() */ gchar* g_parsing_quick_xpath_node_content(xmlXPathContextPtr xpathCtx, gchar *xpath_expr, gchar *attrib, xmlNodePtr base_node) { xmlXPathObjectPtr xpathObj; xmlChar *xmlstr; gchar *str = NULL; if (base_node) { /* (enforce current search node and look for message details) */ xpathCtx->node = base_node; } xpathObj = xmlXPathEvalExpression((xmlChar*) xpath_expr, xpathCtx); if (!xpathObj) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_parsing: Unable to parse response (XPath evaluation).\n"); return NULL; } if (!xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) { if (attrib) xmlstr = xmlGetProp(xpathObj->nodesetval->nodeTab[0], (xmlChar*) attrib); else xmlstr = xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0]); str = g_strdup((gchar*) xmlstr); xmlFree(xmlstr); } xmlXPathFreeObject(xpathObj); return str; } minbif-1.0.5+git20120508/plugins/gayattitude/ga_account.h0000644000175000017500000000117711754151773020770 0ustar sebseb#ifndef GA_GAACCOUNT_H #define GA_GAACCOUNT_H #include #include "../lib/http.h" #define GA_CHECK_INTERVAL 30 typedef struct _GayAttitudeAccount GayAttitudeAccount; struct _GayAttitudeAccount { PurpleAccount *account; PurpleConnection *pc; HttpHandler* http_handler; guint new_messages_check_timer; guint64 latest_msg_id; GHashTable *conv_info; GHashTable *conv_with_buddy_count; }; GayAttitudeAccount* ga_account_new(PurpleAccount *account); void ga_account_free(GayAttitudeAccount* gaa); void ga_account_login(GayAttitudeAccount *gaa); void ga_account_logout(GayAttitudeAccount *gaa); #endif /* GA_GAACCOUNT_H */ minbif-1.0.5+git20120508/plugins/gayattitude/ga_buddy.c0000644000175000017500000001347211754151773020437 0ustar sebseb #include "ga_buddy.h" #include "ga_parsing.h" GayAttitudeBuddy *ga_gabuddy_get_from_buddy(PurpleBuddy *buddy, gboolean create) { GayAttitudeBuddy *gabuddy; if (!buddy) return NULL; purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddy: Looking for GayAttitudeBuddy for buddy '%s'.\n", buddy->name); gabuddy = buddy->proto_data; if (!gabuddy && create) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddy: Creating GayAttitudeBuddy for buddy '%s'.\n", buddy->name); gabuddy = g_new0(GayAttitudeBuddy, 1); gabuddy->buddy = buddy; gabuddy->ref_id = NULL; gabuddy->real_gabuddy = NULL; buddy->proto_data = gabuddy; } return gabuddy; } GayAttitudeBuddy *ga_gabuddy_find(GayAttitudeAccount *gaa, const gchar *buddyname) { PurpleBuddy *buddy; GayAttitudeBuddy *gabuddy; buddy = purple_find_buddy(gaa->account, buddyname); if (!buddy) return NULL; gabuddy = ga_gabuddy_get_from_buddy(buddy, TRUE); return gabuddy; } GayAttitudeBuddy *ga_gabuddy_new(GayAttitudeAccount *gaa, const gchar *buddyname) { PurpleBuddy *buddy; buddy = purple_buddy_new(gaa->account, buddyname, NULL); return ga_gabuddy_get_from_buddy(buddy, TRUE); } void ga_gabuddy_free(GayAttitudeBuddy *gabuddy) { if (!gabuddy) return; g_free(gabuddy); } static void ga_gabuddy_parse_info_cb(HttpHandler* handler, gchar* response, gsize len, gpointer userdata) { htmlDocPtr doc; xmlXPathContextPtr xpathCtx; xmlXPathObjectPtr xpathObj; GayAttitudeAccount *gaa = handler->data; GayAttitudeBuddyInfoRequest *request = userdata; purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_buddy: Fetching info for '%s'.\n", request->gabuddy->buddy->name); doc = htmlReadMemory(response, len, "gayattitude.xml", NULL, 0); if (doc == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddy: Unable to parse response (XML Parsing).\n"); return; } /* Create xpath evaluation context */ xpathCtx = xmlXPathNewContext(doc); if(xpathCtx == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddy: Unable to parse response (XPath context init).\n"); xmlFreeDoc(doc); return; } xmlNode *info_node; /* Search internal Ref ID */ if (!request->gabuddy->ref_id) { purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_buddy: Fetching missing ref_id for '%s'.\n", request->gabuddy->buddy->name); xpathObj = xmlXPathEvalExpression((xmlChar*) "//input[@type='hidden' and @name='ref_id']", xpathCtx); if(xpathObj == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddy: Unable to parse response (XPath evaluation).\n"); xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); return; } if (!xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) { gchar *ref_id; info_node = xpathObj->nodesetval->nodeTab[0]; ref_id = (gchar*) xmlGetProp(info_node, (xmlChar*) "value"); if (request->gabuddy->real_gabuddy) request->gabuddy->real_gabuddy->ref_id = ref_id; else request->gabuddy->ref_id = ref_id; purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_buddy: Found ref_id for '%s': %s.\n", request->gabuddy->buddy->name, request->gabuddy->ref_id); } xmlXPathFreeObject(xpathObj); } if (request->advertise) { PurpleNotifyUserInfo *user_info = purple_notify_user_info_new(); int i; GString *str = NULL; /* Search short description */ xpathCtx->node = doc->parent; xpathObj = xmlXPathEvalExpression((xmlChar*) "//div[@id='PORTRAITHEADER2']/p/text()", xpathCtx); if(xpathObj == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddy: Unable to parse response (XPath evaluation).\n"); xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); return; } if (!xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) { info_node = xpathObj->nodesetval->nodeTab[0]; purple_notify_user_info_add_pair(user_info, "Short Description", (gchar*) info_node->content); } xmlXPathFreeObject(xpathObj); /* Search user research */ xpathCtx->node = doc->parent; xpathObj = xmlXPathEvalExpression((xmlChar*) "//div[@id='bloc_recherche']/p/text()", xpathCtx); if(xpathObj == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddy: Unable to parse response (XPath evaluation).\n"); xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); return; } if (!xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) { for(i = 0; i < xpathObj->nodesetval->nodeNr; i++) { info_node = xpathObj->nodesetval->nodeTab[i]; if (i == 0) str = g_string_new((gchar*) info_node->content); else g_string_append_printf(str, " -- %s", info_node->content); } purple_notify_user_info_add_pair(user_info, "Research", str->str); g_string_free(str, TRUE); } xmlXPathFreeObject(xpathObj); purple_notify_userinfo(gaa->pc, request->gabuddy->buddy->name, user_info, NULL, NULL); purple_notify_user_info_destroy(user_info); } /* Cleanup */ xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); /* Chained Callback */ if (request->callback) { purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_buddy: Calling callback after info for '%s' was retrieved\n", request->gabuddy->buddy->name); request->callback(gaa, request->callback_data); } } void ga_gabuddy_request_info(GayAttitudeAccount* gaa, GayAttitudeBuddy *gabuddy, gboolean advertise, GayAttitudeRequestInfoCallbackFunc callback, gpointer callback_data) { gchar *url_path, *buddy_name; GayAttitudeBuddyInfoRequest *request = g_new0(GayAttitudeBuddyInfoRequest, 1); if (gabuddy->real_gabuddy) buddy_name = gabuddy->real_gabuddy->buddy->name; else buddy_name = gabuddy->buddy->name; url_path = g_strdup_printf("/%s", buddy_name); request->gabuddy = gabuddy; request->advertise = advertise; request->callback = callback; request->callback_data = callback_data; http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME_PERSO, url_path, NULL, ga_gabuddy_parse_info_cb, (gpointer) request, FALSE); g_free(url_path); } minbif-1.0.5+git20120508/plugins/gayattitude/gayattitude.c0000644000175000017500000001706711754151773021211 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Marc Dequènes, Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "gayattitude.h" PurplePlugin *_gayattitude_plugin = NULL; static const char *ga_plugin_blist_icon(PurpleAccount *a, PurpleBuddy *b) { return "gayattitude"; } static void ga_plugin_login(PurpleAccount *account) { GayAttitudeAccount* gaa; gaa = ga_account_new(account); ga_account_login(gaa); } static void ga_plugin_close(PurpleConnection *gc) { g_return_if_fail(gc != NULL); g_return_if_fail(gc->proto_data != NULL); GayAttitudeAccount* gaa = gc->proto_data; ga_account_logout(gaa); ga_account_free(gaa); } static void ga_plugin_get_info(PurpleConnection *gc, const char *who) { GayAttitudeAccount* gaa = gc->proto_data; GayAttitudeBuddy *gabuddy; gabuddy = ga_gabuddy_find(gaa, who); if (!gabuddy) gabuddy = ga_gabuddy_new(gaa, who); ga_gabuddy_request_info(gaa, gabuddy, TRUE, NULL, NULL); } static int ga_plugin_send_im(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags) { GayAttitudeAccount* gaa = gc->proto_data; GayAttitudeBuddy *gabuddy; gabuddy = ga_gabuddy_find(gaa, who); if (!gabuddy) gabuddy = ga_gabuddy_new(gaa, who); return ga_message_send(gaa, gabuddy, what, flags); } static GList *ga_plugin_status_types(PurpleAccount *account) { PurpleStatusType *type; GList *types = NULL; type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE); types = g_list_append(types, type); return types; } static void ga_plugin_set_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *gc = purple_account_get_connection(account); //const char *status_id = purple_status_get_id(status); g_return_if_fail(gc != NULL); if (!purple_status_is_active(status)) return; } static void ga_plugin_keepalive(PurpleConnection *gc) { } static void ga_plugin_buddy_free(PurpleBuddy *buddy) { ga_gabuddy_free(buddy->proto_data); buddy->proto_data = NULL; } static void ga_plugin_conv_closed(PurpleConnection *gc, const char *who) { GayAttitudeAccount* gaa = gc->proto_data; ga_message_close_conversation(gaa, who); } static PurplePluginProtocolInfo ga_plugin_prpl_info = { OPT_PROTO_PASSWORD_OPTIONAL, NULL, /* user_splits */ NULL, /* protocol_options */ NO_BUDDY_ICONS, /* icon_spec */ ga_plugin_blist_icon, /* list_icon */ NULL, /* list_emblems */ NULL, /* status_text */ NULL, /* tooltip_text */ ga_plugin_status_types, /* away_states */ NULL, /* blist_node_menu */ NULL, /* chat_info */ NULL, /* chat_info_defaults */ ga_plugin_login, /* login */ ga_plugin_close, /* close */ ga_plugin_send_im, /* send_im */ NULL, /* set_info */ NULL, /* send_typing */ ga_plugin_get_info, /* get_info */ ga_plugin_set_status, /* set_status */ NULL, /* set_idle */ NULL, /* change_passwd */ NULL, /* add_buddy */ NULL, /* add_buddies */ NULL, /* remove_buddy */ NULL, /* remove_buddies */ NULL, /* add_permit */ NULL, /* add_deny */ NULL, /* rem_permit */ NULL, /* rem_deny */ NULL, /* set_permit_deny */ NULL, /* join_chat */ NULL, /* reject_chat */ NULL, /* get_chat_name */ NULL, /* chat_invite */ NULL, /* chat_leave */ NULL, /* chat_whisper */ NULL, /* chat_send */ ga_plugin_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */ ga_plugin_buddy_free, /* buddy_free */ ga_plugin_conv_closed, /* convo_closed */ purple_normalize_nocase, /* normalize */ NULL, /* set_buddy_icon */ NULL, /* remove_group */ NULL, /* get_cb_real_name */ NULL, /* set_chat_topic */ NULL, /* find_blist_chat */ NULL, /* roomlist_get_list */ NULL, /* roomlist_cancel */ NULL, /* roomlist_expand_category */ NULL, /* can_receive_file */ NULL, /* send_file */ NULL, /* new_xfer */ NULL, /* offline_message */ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL, /* get_account_text_table */ NULL, /* initiate_media */ NULL /* can_do_media */ #if (PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 7) , NULL, /* get_moods */ NULL, /* set_public_alias */ NULL /* get_public_alias */ #endif #if (PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 8) , NULL, /* add_buddy_with_invite */ NULL /* add_budies_with_invite */ #endif }; static gboolean ga_plugin_load (PurplePlugin *plugin) { return TRUE; } static PurplePluginInfo ga_plugin_info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_PROTOCOL, /**< type */ NULL, /**< ui_requirement */ 0, /**< flags */ NULL, /**< dependencies */ PURPLE_PRIORITY_DEFAULT, /**< priority */ "prpl-gayattitude", /**< id */ "GayAttitude", /**< name */ "1.0", /**< version */ "gayattitude Protocol Plugin", /** summary */ "www.gayattitude.com chat", /** description */ "Marc Dequènes, Romain Bignon", /**< author */ "http://minbif.im", /**< homepage */ ga_plugin_load, /**< load */ NULL, /**< unload */ NULL, /**< destroy */ NULL, /**< ui_info */ &ga_plugin_prpl_info, /**< extra_info */ NULL, /**< prefs_info */ NULL, /* padding */ NULL, NULL, NULL, NULL }; static void ga_plugin_init(PurplePlugin *plugin) { PurpleAccountOption *option; GHashTable *ui_info = purple_core_get_ui_info(); const gchar *ui_name = g_hash_table_lookup(ui_info, "version"); if(!ui_name) ui_name = GA_NAME; option = purple_account_option_string_new("User-agent", "user-agent", ui_name); ga_plugin_prpl_info.protocol_options = g_list_append(ga_plugin_prpl_info.protocol_options, option); _gayattitude_plugin = plugin; } PURPLE_INIT_PLUGIN(gayattitude, ga_plugin_init, ga_plugin_info); minbif-1.0.5+git20120508/plugins/gayattitude/ga_buddy.h0000644000175000017500000000207511754151773020441 0ustar sebseb#ifndef GA_GABUDDY_H #define GA_GABUDDY_H #include #include "ga_account.h" typedef struct _GayAttitudeBuddy GayAttitudeBuddy; typedef struct _GayAttitudeBuddyInfoRequest GayAttitudeBuddyInfoRequest; typedef void (*GayAttitudeRequestInfoCallbackFunc)(GayAttitudeAccount* gaa, gpointer user_data); struct _GayAttitudeBuddy { PurpleBuddy *buddy; gchar *ref_id; GayAttitudeBuddy *real_gabuddy; }; struct _GayAttitudeBuddyInfoRequest { GayAttitudeBuddy *gabuddy; gboolean advertise; GayAttitudeRequestInfoCallbackFunc callback; gpointer callback_data; }; GayAttitudeBuddy *ga_gabuddy_get_from_buddy(PurpleBuddy *buddy, gboolean create); GayAttitudeBuddy *ga_gabuddy_find(GayAttitudeAccount *gaa, const gchar *gabuddyname); GayAttitudeBuddy *ga_gabuddy_new(GayAttitudeAccount *gaa, const gchar *buddyname); void ga_gabuddy_free(GayAttitudeBuddy *gabuddy); void ga_gabuddy_request_info(GayAttitudeAccount* gaa, GayAttitudeBuddy *gabuddy, gboolean advertise, GayAttitudeRequestInfoCallbackFunc callback, gpointer callback_data); #endif /* GA_GABUDDY_H */ minbif-1.0.5+git20120508/plugins/gayattitude/ga_message.c0000644000175000017500000002700511754151773020751 0ustar sebseb #define _GNU_SOURCE #include "ga_message.h" #include "ga_parsing.h" #include static int ga_message_send_real(GayAttitudeAccount *gaa, GayAttitudeBuddy *gabuddy, const char *what, PurpleMessageFlags flags) { gchar *url_path, *postdata, *msg; GayAttitudeConversationInfo *conv_info; purple_debug_info("gayattitude", "ga_message: about to send message to '%s'.\n", gabuddy->buddy->name); conv_info = g_hash_table_lookup(gaa->conv_info, gabuddy->buddy->name); msg = http_url_encode(what, TRUE); if (conv_info && conv_info->latest_msg_id) { url_path = conv_info->url_path; postdata = g_strdup_printf("host=&id=%" G_GUINT64_FORMAT "&checksum=%s&text=%s&submit=Envoyer&fond=", conv_info->latest_msg_id, conv_info->checksum, msg); } else { if (!gabuddy->ref_id) { purple_debug_error("gayattitude", "ga_message: could not find ref_id for buddy '%s'\n", gabuddy->buddy->name); return 1; } url_path = g_strdup_printf("/html/portrait/message?p=%s&pid=%s&host=&smallheader=&popup=0", gabuddy->buddy->name, gabuddy->ref_id); postdata = g_strdup_printf("msg=%s&sendchat=Envoyer+(Shift-Entr%%82e)&fond=&sendmail=0", msg); } http_post_or_get(gaa->http_handler, HTTP_METHOD_POST, GA_HOSTNAME, url_path, postdata, NULL, NULL, FALSE); purple_debug_info("gayattitude", "ga_message: sending message to '%s'\n", gabuddy->buddy->name); g_free(msg); g_free(postdata); if (conv_info && conv_info->latest_msg_id) conv_info->replied = TRUE; else g_free(url_path); /* TODO: wait until successful HTTP reply */ purple_conversation_write(conv_info->conv, gabuddy->buddy->name, what, PURPLE_MESSAGE_SEND, 0); return 0; } static void ga_message_send_delayed_cb(GayAttitudeAccount *gaa, GayAttitudeDelayedMessageRequest *delayed_msg) { purple_debug_info("gayattitude", "ga_message: prepare to send delayed message to '%s'\n", delayed_msg->gabuddy->buddy->name); ga_message_send_real(gaa, delayed_msg->gabuddy, delayed_msg->what, delayed_msg->flags); g_free(delayed_msg); } int ga_message_send(GayAttitudeAccount *gaa, GayAttitudeBuddy *gabuddy, const char *what, PurpleMessageFlags flags) { GayAttitudeConversationInfo *conv_info; conv_info = g_hash_table_lookup(gaa->conv_info, gabuddy->buddy->name); if (conv_info) { if (conv_info->replied) { purple_conversation_write(conv_info->conv, gabuddy->buddy->name, "You cannot reply again to this thread. Wait for a reply or open a new thread.", PURPLE_MESSAGE_SYSTEM, 0); return 1; } } else { if (!gabuddy->ref_id) { purple_debug_error("gayattitude", "ga_message: ref_id for buddy '%s' is unknown, starting lookup for delayed message\n", gabuddy->buddy->name); GayAttitudeDelayedMessageRequest *delayed_msg = g_new0(GayAttitudeDelayedMessageRequest, 1); delayed_msg->gaa = gaa; delayed_msg->gabuddy = gabuddy; delayed_msg->what = g_strdup(what); delayed_msg->flags = flags; ga_gabuddy_request_info(gaa, gabuddy, FALSE, (GayAttitudeRequestInfoCallbackFunc) ga_message_send_delayed_cb, (gpointer) delayed_msg); return 0; } } ga_message_send_real(gaa, gabuddy, what, flags); return 0; } static void ga_message_extra_info_cb(HttpHandler* handler, gchar* response, gsize len, gpointer userdata) { htmlDocPtr doc; xmlXPathContextPtr xpathCtx; GayAttitudeAccount* gaa = handler->data; GayAttitudeMessageExtraInfo* extra_info = (GayAttitudeMessageExtraInfo*)userdata; gchar *message_strtimestamp, *message_url_path, *message_checksum; struct tm *message_tm; time_t message_timestamp; GayAttitudeConversationInfo *conv_info; purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: looking for extra message info for '%" G_GUINT64_FORMAT "'.\n", extra_info->id); doc = htmlReadMemory(response, len, "gayattitude.xml", NULL, 0); if (doc == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_message: Unable to parse response (XML Parsing).\n"); return; } /* Create xpath evaluation context */ xpathCtx = xmlXPathNewContext(doc); if(xpathCtx == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_message: Unable to parse response (XPath context init).\n"); xmlFreeDoc(doc); return; } message_strtimestamp = g_parsing_quick_xpath_node_content(xpathCtx, "//p[@id='MESSAGESTAMP']", NULL, NULL); if (message_strtimestamp) g_strstrip(message_strtimestamp); message_url_path = g_parsing_quick_xpath_node_content(xpathCtx, "//form[@id='form_reply']", "action", NULL); message_checksum = g_parsing_quick_xpath_node_content(xpathCtx, "//form[@id='form_reply']/input[@name='checksum']", "value", NULL); purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message strtimestamp: %s\n", message_strtimestamp); purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message url_path: %s\n", message_url_path); purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message checksum: %s\n", message_checksum); /* TODO: fetch history */ if (message_strtimestamp && message_url_path && message_checksum) { /* compute timestamp */ message_tm = g_new0(struct tm, 1); strptime(message_strtimestamp, "%d/%m/%y - %H:%M", message_tm); message_timestamp = mktime(message_tm); g_free(message_tm); purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message timestamp: %u\n", (guint) message_timestamp); /* store extra message info */ conv_info = g_hash_table_lookup(gaa->conv_info, extra_info->conv_name); if (conv_info->url_path) g_free(conv_info->url_path); conv_info->url_path = message_url_path; if (conv_info->checksum) g_free(conv_info->checksum); conv_info->checksum = message_checksum; conv_info->timestamp = message_timestamp; /* Create conversation if necessary and send message*/ purple_conversation_write(conv_info->conv, extra_info->sender, extra_info->msg, PURPLE_MESSAGE_RECV, message_timestamp); } else purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: parsing extra message info for '%" G_GUINT64_FORMAT "' failed.\n", extra_info->id); } static void ga_message_received_cb(HttpHandler* handler, gchar* response, gsize len, gpointer userdata) { htmlDocPtr doc; xmlXPathContextPtr xpathCtx; xmlXPathObjectPtr xpathObj; GayAttitudeAccount* gaa = handler->data; int i; purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: looking for received messages.\n"); doc = htmlReadMemory(response, len, "gayattitude.xml", NULL, 0); if (doc == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_message: Unable to parse response (XML Parsing).\n"); return; } /* Create xpath evaluation context */ xpathCtx = xmlXPathNewContext(doc); if(xpathCtx == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_message: Unable to parse response (XPath context init).\n"); xmlFreeDoc(doc); return; } /* Evaluate xpath expression */ xpathObj = xmlXPathEvalExpression((xmlChar*) "//div[@id='LIST']/table[1]/tr", xpathCtx); if(xpathObj == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_message: Unable to parse response (XPath evaluation).\n"); xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); return; } if (!xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) { /* Print results */ xmlNodeSetPtr nodes = xpathObj->nodesetval; purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: number of nodes found: %u\n", nodes->nodeNr); xmlNode *message_node; guint64 new_latest_msg_id = gaa->latest_msg_id; for(i = 0; i < nodes->nodeNr; i++) { purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message %u\n", i); message_node = nodes->nodeTab[i]; /* Message parsing */ gchar *message_idstr, *message_sender, *message_content; message_idstr = g_parsing_quick_xpath_node_content(xpathCtx, "./td[1]/input", "name", message_node); message_sender = g_parsing_quick_xpath_node_content(xpathCtx, "./td[3]/a", NULL, message_node); message_content = g_parsing_quick_xpath_node_content(xpathCtx, "./td[4]/a", NULL, message_node); /* check if ID is valid */ guint64 message_id = 0; if (message_idstr) { if (g_str_has_prefix(message_idstr, "msg")) message_id = g_ascii_strtoull(message_idstr + 3, NULL, 10); g_free(message_idstr); } purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message id: %" G_GUINT64_FORMAT "\n", message_id); purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message sender: %s\n", message_sender); purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: message content: %s\n", message_content); if (message_id && message_sender && message_content) { if (message_id > gaa->latest_msg_id) { guint *conv_count; gchar *conv_name, *extra_info_url_path; PurpleConversation *conv; GayAttitudeMessageExtraInfo *msg_extra_info; /* Count number of conversation threads with the same user, in order to differenciate them */ /* This value is not stored in the GayAttitudeBuddy, as you can talk with someone not in your buddylist */ conv_count = g_hash_table_lookup(gaa->conv_with_buddy_count, message_sender); if (!conv_count) { conv_count = g_new0(guint, 1); g_hash_table_insert(gaa->conv_with_buddy_count, message_sender, conv_count); } *conv_count += 1; /* Generate conversation name */ conv_name = g_strdup_printf("%s@%u", message_sender, *conv_count); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, conv_name, gaa->account); if (!conv) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, gaa->account, conv_name); /* Store conversation name<->message id association, to allow sending replies to the proper thread */ GayAttitudeConversationInfo *conv_info = g_new0(GayAttitudeConversationInfo, 1); conv_info->conv = conv; conv_info->replied = FALSE; conv_info->latest_msg_id = message_id; g_hash_table_insert(gaa->conv_info, conv_name, conv_info); /* Fetch extra info and then deliver the received message */ extra_info_url_path = g_strdup_printf("/html/perso/chat/message?id=%" G_GUINT64_FORMAT "&popup=NONLUS", message_id); msg_extra_info = g_new(GayAttitudeMessageExtraInfo, 1); msg_extra_info->id = message_id; msg_extra_info->sender = message_sender; msg_extra_info->msg = message_content; msg_extra_info->conv_name = conv_name; http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, extra_info_url_path, NULL, ga_message_extra_info_cb, msg_extra_info, FALSE); g_free(extra_info_url_path); if (message_id > new_latest_msg_id) new_latest_msg_id = message_id; } else purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: skipped message from %s with id %" G_GUINT64_FORMAT "\n", message_sender, message_id); } else purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_message: parsing message #%u from received list failed\n", i); } gaa->latest_msg_id = new_latest_msg_id; } /* Cleanup */ xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); } void ga_message_check_received(GayAttitudeAccount *gaa) { http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/perso/chat/nonlus/?mode=to", NULL, ga_message_received_cb, NULL, FALSE); } void ga_message_close_conversation(GayAttitudeAccount *gaa, const gchar *conv_name) { GayAttitudeConversationInfo *conv_info; conv_info = g_hash_table_lookup(gaa->conv_info, conv_name); if (conv_info) { if (conv_info->url_path) g_free(conv_info->url_path); if (conv_info->checksum) g_free(conv_info->checksum); g_hash_table_remove(gaa->conv_info, conv_name); } else purple_debug_error("gayattitude", "ga_message: trying to close unexisting conversation '%s'.\n", conv_name); } minbif-1.0.5+git20120508/plugins/gayattitude/ga_buddylist.c0000644000175000017500000001030111754151773021317 0ustar sebseb #include "ga_buddylist.h" #include "ga_buddy.h" #include "ga_parsing.h" #include static void ga_buddylist_parse_cb(HttpHandler* handler, gchar* response, gsize len, gpointer userdata) { htmlDocPtr doc; xmlXPathContextPtr xpathCtx; xmlXPathObjectPtr xpathObj; GayAttitudeAccount* gaa = handler->data; gchar* group_name = userdata; doc = htmlReadMemory(response, len, "gayattitude.xml", NULL, 0); if (doc == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddylist: Unable to parse response (XML Parsing).\n"); return; } /* Create xpath evaluation context */ xpathCtx = xmlXPathNewContext(doc); if(xpathCtx == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddylist: Unable to parse response (XPath context init).\n"); xmlFreeDoc(doc); return; } /* Evaluate xpath expression */ xpathObj = xmlXPathEvalExpression((xmlChar*) "//div[@id='ANNUAIRE']/div[@id='RESULTATS']/div", xpathCtx); if(xpathObj == NULL) { purple_debug(PURPLE_DEBUG_ERROR, "gayattitude", "ga_buddylist: Unable to parse response (XPath evaluation).\n"); xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); return; } if (!xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) { /* Print results */ xmlNodeSetPtr nodes = xpathObj->nodesetval; purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_buddylist: number of nodes found: %i\n", nodes->nodeNr); PurpleGroup *group = NULL; if (group_name) { group = purple_find_group(group_name); if (!group) { group = purple_group_new(group_name); purple_blist_add_group(group, NULL); } g_free(group_name); } int i; xmlChar *xmlprop = NULL; xmlNode *contact_node; for(i = 0; i < nodes->nodeNr; i++) { contact_node = nodes->nodeTab[i]; xmlprop = xmlGetProp(contact_node, (xmlChar*) "class"); /* look for contacts */ if (xmlprop && g_str_has_prefix((gchar*) xmlprop, "ITEM")) { gchar *contact_name; GayAttitudeBuddy *gabuddy; contact_name = g_parsing_quick_xpath_node_content(xpathCtx, "./div[@class='ITEM2']/div[@class='pseudo']/a", NULL, contact_node); if (contact_name) { purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_buddylist: found buddy from server: %s\n", contact_name); gabuddy = ga_gabuddy_find(gaa, contact_name); if (!gabuddy) { gabuddy = ga_gabuddy_new(gaa, contact_name); purple_blist_add_buddy(gabuddy->buddy, NULL, group, NULL); purple_debug(PURPLE_DEBUG_INFO, "gayattitude", "ga_buddylist: added missing buddy: %s\n", contact_name); } if (strstr((char*) xmlprop, "ITEMONLINE")) purple_prpl_got_user_status(gaa->account, contact_name, "available", NULL); else purple_prpl_got_user_status(gaa->account, contact_name, "offline", NULL); g_free(contact_name); } } xmlFree(xmlprop); } } /* Cleanup */ xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); xmlFreeDoc(doc); } void ga_buddylist_update(GayAttitudeAccount* gaa) { http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/annuaire/liste?liste=contacts-checklist", NULL, ga_buddylist_parse_cb, g_strdup("Checklist"), FALSE); http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/annuaire/liste?liste=contacts-friendlist", NULL, ga_buddylist_parse_cb, g_strdup("Friendlist"), FALSE); http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/annuaire/liste?liste=contacts-hotlist", NULL, ga_buddylist_parse_cb, g_strdup("Hotlist"), FALSE); http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/annuaire/liste?liste=contacts-blogolist", NULL, ga_buddylist_parse_cb, g_strdup("Blogolist"), FALSE); http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/annuaire/liste?liste=contacts-blacklist", NULL, ga_buddylist_parse_cb, g_strdup("Blacklist"), FALSE); http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/annuaire/liste?liste=contacts-whitelist", NULL, ga_buddylist_parse_cb, g_strdup("Whitelist"), FALSE); } void ga_buddylist_check_status(GayAttitudeAccount* gaa) { http_post_or_get(gaa->http_handler, HTTP_METHOD_GET, GA_HOSTNAME, "/html/annuaire/liste?liste=contacts-online", NULL, ga_buddylist_parse_cb, NULL, FALSE); } minbif-1.0.5+git20120508/plugins/gayattitude/CMakeLists.txt0000644000175000017500000000032411754151773021245 0ustar sebsebADD_LIBRARY(gayattitude SHARED ga_parsing.c ga_account.c ga_buddy.c ga_buddylist.c ga_message.c gayattitude.c ) TARGET_LINK_LIBRARIES(gayattitude ${PURPLE_LIBRARIES} ${LIBXML_LIBRARIES} pluginlib) minbif-1.0.5+git20120508/plugins/gayattitude/gayattitude.h0000644000175000017500000000176211754151773021211 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009 Marc Dequènes, Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef GA_GAYATTITUDE_H #define GA_GAYATTITUDE_H #include #include "../lib/http.h" #include "ga_account.h" #include "ga_buddy.h" #include "ga_message.h" #define GA_NAME "libpurple (gayattitude)" #endif /* GA_GAYATTITUDE_H */ minbif-1.0.5+git20120508/plugins/gayattitude/ga_account.c0000644000175000017500000000516511754151773020764 0ustar sebseb #include "ga_account.h" #include "ga_buddylist.h" #include "ga_message.h" #include "ga_parsing.h" GayAttitudeAccount* ga_account_new(PurpleAccount *account) { GayAttitudeAccount* gaa; gaa = g_new0(GayAttitudeAccount, 1); gaa->account = account; gaa->pc = purple_account_get_connection(account); gaa->http_handler = http_handler_new(account, gaa); gaa->latest_msg_id = 0; gaa->conv_info = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); gaa->conv_with_buddy_count = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); account->gc->proto_data = gaa; /* basic init */ gaa->pc->flags |= PURPLE_CONNECTION_NO_NEWLINES; purple_connection_set_display_name(gaa->pc, purple_account_get_username(account)); return gaa; } void ga_account_free(GayAttitudeAccount* gaa) { if (gaa->new_messages_check_timer) { purple_timeout_remove(gaa->new_messages_check_timer); } http_handler_free(gaa->http_handler); g_hash_table_destroy(gaa->conv_info); g_hash_table_destroy(gaa->conv_with_buddy_count); g_free(gaa); } static void ga_account_check_changes(GayAttitudeAccount* gaa) { ga_buddylist_check_status(gaa); ga_message_check_received(gaa); } static void ga_account_login_cb(HttpHandler* handler, gchar *response, gsize len, gpointer userdata) { GayAttitudeAccount *gaa = handler->data; purple_connection_update_progress(gaa->pc, "Authenticating", 2, 3); if (!g_hash_table_lookup(gaa->http_handler->cookie_table, "cookielogin")) { purple_connection_error_reason(gaa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Could not log in GA. (check 'username' and 'password' settings)"); } else { purple_connection_set_state(gaa->pc, PURPLE_CONNECTED); ga_buddylist_update(gaa); ga_account_check_changes(gaa); gaa->new_messages_check_timer = g_timeout_add_seconds(GA_CHECK_INTERVAL, (GSourceFunc)ga_account_check_changes, gaa); } } void ga_account_login(GayAttitudeAccount *gaa) { gchar *postdata; const char *username = purple_account_get_username(gaa->account); const char *password = purple_account_get_password(gaa->account); purple_connection_set_state(gaa->pc, PURPLE_CONNECTING); purple_connection_update_progress(gaa->pc, "Connecting", 1, 3); gchar* encoded_password = http_url_encode(password, TRUE); postdata = g_strdup_printf("login=%s&passw=%s", username, encoded_password); http_post_or_get(gaa->http_handler, HTTP_METHOD_POST , GA_HOSTNAME, "/html/login", postdata, ga_account_login_cb, NULL, FALSE); g_free(encoded_password); g_free(postdata); } void ga_account_logout(GayAttitudeAccount *gaa) { /* TODO */ purple_connection_set_state(gaa->pc, PURPLE_DISCONNECTED); } minbif-1.0.5+git20120508/plugins/gayattitude/ga_parsing.h0000644000175000017500000000071611754151773020775 0ustar sebseb#ifndef GA_GAPARSING_H #define GA_GAPARSING_H #include #include #include #include #include #include #define GA_HOSTNAME "www.gayattitude.com" #define GA_HOSTNAME_PERSO "perso.gayattitude.com" gchar* g_parsing_quick_xpath_node_content(xmlXPathContextPtr xpathCtx, gchar *xpath_expr, gchar *attrib, xmlNodePtr base_node); #endif /* GA_GAPARSING_H */ minbif-1.0.5+git20120508/plugins/gayattitude/TODO0000644000175000017500000000017711754151773017203 0ustar sebseb - handle unexpected disconnections - logout - ensure we don't run out of conv count, as there is not reassignation mechanism minbif-1.0.5+git20120508/plugins/lib/0000755000175000017500000000000011754151773014730 5ustar sebsebminbif-1.0.5+git20120508/plugins/lib/http.c0000644000175000017500000004614411754151773016064 0ustar sebseb/* * libfacebook is the property of its developers. See the COPYRIGHT file * for more details. * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "http.h" #include #include #include #include #include #include static void http_attempt_connection(HttpConnection *); HttpHandler* http_handler_new(PurpleAccount* account, void* data) { HttpHandler* handler = g_new0(HttpHandler, 1); handler->account = account; handler->pc = purple_account_get_connection(account); handler->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); handler->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); handler->data = data; return handler; } void http_handler_free(HttpHandler* handler) { purple_debug_info("httpproxy", "destroying %d incomplete connections\n", g_slist_length(handler->conns)); while (handler->conns != NULL) http_connection_destroy(handler->conns->data); while (handler->dns_queries != NULL) { PurpleDnsQueryData *dns_query = handler->dns_queries->data; purple_debug_info("httpproxy", "canceling dns query for %s\n", purple_dnsquery_get_host(dns_query)); handler->dns_queries = g_slist_remove(handler->dns_queries, dns_query); purple_dnsquery_destroy(dns_query); } g_hash_table_destroy(handler->cookie_table); g_hash_table_destroy(handler->hostname_ip_cache); g_free(handler); } #ifdef HAVE_ZLIB static guchar *http_gunzip(const guchar *gzip_data, ssize_t *len_ptr) { gsize gzip_data_len = *len_ptr; z_stream zstr; int gzip_err = 0; guchar *output_data; gulong gzip_len = G_MAXUINT16; g_return_val_if_fail(zlib_inflate != NULL, NULL); output_data = g_new0(guchar, gzip_len); zstr.next_in = gzip_data; zstr.avail_in = gzip_data_len; zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; int flags = gzip_data[3]; int offset = 4; /* if (flags & 0x04) offset += *tmp[] */ zstr.next_in += offset; zstr.avail_in -= offset; zlib_inflateInit2_(&zstr, -MAX_WBITS, ZLIB_VERSION, sizeof(z_stream)); zstr.next_out = output_data; zstr.avail_out = gzip_len; gzip_err = zlib_inflate(&zstr, Z_FINISH); zlib_inflateEnd(&zstr); purple_debug_info("httpproxy", "gzip len: %ld, len: %ld\n", gzip_len, gzip_data_len); purple_debug_info("httpproxy", "gzip flags: %d\n", flags); purple_debug_info("httpproxy", "gzip error: %d\n", gzip_err); *len_ptr = gzip_len; return output_data; } #endif void http_connection_destroy(HttpConnection *fbconn) { fbconn->handler->conns = g_slist_remove(fbconn->handler->conns, fbconn); if (fbconn->request != NULL) g_string_free(fbconn->request, TRUE); g_free(fbconn->rx_buf); if (fbconn->connect_data != NULL) purple_proxy_connect_cancel(fbconn->connect_data); if (fbconn->ssl_conn != NULL) purple_ssl_close(fbconn->ssl_conn); if (fbconn->fd >= 0) { close(fbconn->fd); } if (fbconn->input_watcher > 0) purple_input_remove(fbconn->input_watcher); g_free(fbconn->hostname); g_free(fbconn); } static void http_update_cookies(HttpHandler *handler, const gchar *headers) { const gchar *cookie_start; const gchar *cookie_end; gchar *cookie_name; gchar *cookie_value; int header_len; g_return_if_fail(headers != NULL); header_len = strlen(headers); /* look for the next "Set-Cookie: " */ /* grab the data up until ';' */ cookie_start = headers; while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && (headers-cookie_start) < header_len) { cookie_start += 14; cookie_end = strchr(cookie_start, '='); cookie_name = g_strndup(cookie_start, cookie_end-cookie_start); cookie_start = cookie_end + 1; cookie_end = strchr(cookie_start, ';'); cookie_value= g_strndup(cookie_start, cookie_end-cookie_start); cookie_start = cookie_end; purple_debug_info("httpproxy", "got cookie %s=%s\n", cookie_name, cookie_value); g_hash_table_replace(handler->cookie_table, cookie_name, cookie_value); } } static void http_connection_process_data(HttpConnection *fbconn) { ssize_t len; gchar *tmp; len = fbconn->rx_len; tmp = g_strstr_len(fbconn->rx_buf, len, "\r\n\r\n"); if (tmp == NULL) { /* This is a corner case that occurs when the connection is * prematurely closed either on the client or the server. * This can either be no data at all or a partial set of * headers. We pass along the data to be good, but don't * do any fancy massaging. In all likelihood the result will * be tossed by the connection callback func anyways */ tmp = g_strndup(fbconn->rx_buf, len); } else { tmp += 4; len -= g_strstr_len(fbconn->rx_buf, len, "\r\n\r\n") - fbconn->rx_buf + 4; tmp = g_memdup(tmp, len + 1); tmp[len] = '\0'; fbconn->rx_buf[fbconn->rx_len - len] = '\0'; purple_debug_misc("httpproxy", "response headers\n%s\n", fbconn->rx_buf); http_update_cookies(fbconn->handler, fbconn->rx_buf); #ifdef HAVE_ZLIB if (strstr(fbconn->rx_buf, "Content-Encoding: gzip")) { /* we've received compressed gzip data, decompress */ if (zlib_inflate != NULL) { gchar *gunzipped; gunzipped = http_gunzip((const guchar *)tmp, &len); g_free(tmp); tmp = gunzipped; } } #endif } g_free(fbconn->rx_buf); fbconn->rx_buf = NULL; if (fbconn->callback != NULL) fbconn->callback(fbconn->handler, tmp, len, fbconn->user_data); g_free(tmp); } static void http_fatal_connection_cb(HttpConnection *fbconn) { PurpleConnection *pc = fbconn->handler->pc; purple_debug_error("httpproxy", "fatal connection error\n"); http_connection_destroy(fbconn); /* We died. Do not pass Go. Do not collect $200 */ /* In all seriousness, don't attempt to call the normal callback here. * That may lead to the wrong error message being displayed */ purple_connection_error_reason(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "Server closed the connection."); } static void http_post_or_get_readdata_cb(gpointer data, gint source, PurpleInputCondition cond) { HttpConnection *fbconn; gchar buf[4096]; ssize_t len; fbconn = data; if (fbconn->method & HTTP_METHOD_SSL) { len = purple_ssl_read(fbconn->ssl_conn, buf, sizeof(buf) - 1); } else { len = recv(fbconn->fd, buf, sizeof(buf) - 1, 0); } if (len < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { /* Try again later */ return; } if (fbconn->method & HTTP_METHOD_SSL && fbconn->rx_len > 0) { /* * This is a slightly hacky workaround for a bug in either * GNU TLS or in the SSL implementation on Http's web * servers. The sequence of events is: * 1. We attempt to read the first time and successfully read * the server's response. * 2. We attempt to read a second time and libpurple's call * to gnutls_record_recv() returns the error * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or * "A TLS packet with unexpected length was received." * * Normally the server would have closed the connection * cleanly and this second read() request would have returned * 0. Or maybe it's normal for SSL connections to be severed * in this manner? In any case, this differs from the behavior * of the standard recv() system call. */ purple_debug_warning("httpproxy", "ssl error, but data received. attempting to continue\n"); } else { /* TODO: Is this a regular occurrence? If so then maybe resend the request? */ http_fatal_connection_cb(fbconn); return; } } if (len > 0) { buf[len] = '\0'; fbconn->rx_buf = g_realloc(fbconn->rx_buf, fbconn->rx_len + len + 1); memcpy(fbconn->rx_buf + fbconn->rx_len, buf, len + 1); fbconn->rx_len += len; /* Wait for more data before processing */ return; } /* The server closed the connection, let's parse the data */ http_connection_process_data(fbconn); http_connection_destroy(fbconn); } static void http_post_or_get_ssl_readdata_cb (gpointer data, PurpleSslConnection *ssl, PurpleInputCondition cond) { http_post_or_get_readdata_cb(data, -1, cond); } static void http_post_or_get_connect_cb(gpointer data, gint source, const gchar *error_message) { HttpConnection *fbconn; ssize_t len; fbconn = data; fbconn->connect_data = NULL; if (error_message) { purple_debug_error("httpproxy", "post_or_get_connect_cb %s\n", error_message); http_fatal_connection_cb(fbconn); return; } purple_debug_info("httpproxy", "post_or_get_connect_cb\n"); fbconn->fd = source; len = write(fbconn->fd, fbconn->request->str, fbconn->request->len); if (len < 0 && errno == EAGAIN) return; else if (len <= 0) { purple_debug_error("httpproxy", "post_or_get_connect_cb %s\n", g_strerror(errno)); http_fatal_connection_cb(fbconn); return; } fbconn->input_watcher = purple_input_add(fbconn->fd, PURPLE_INPUT_READ, http_post_or_get_readdata_cb, fbconn); } static void http_post_or_get_ssl_connect_cb(gpointer data, PurpleSslConnection *ssl, PurpleInputCondition cond) { HttpConnection *fbconn; ssize_t len; fbconn = data; purple_debug_info("httpproxy", "post_or_get_ssl_connect_cb\n"); len = purple_ssl_write(fbconn->ssl_conn, fbconn->request->str, fbconn->request->len); if (len < 0 && errno == EAGAIN) return; else if (len <= 0) { purple_debug_error("httpproxy", "post_or_get_ssl_connect_cb %s\n", g_strerror(errno)); http_fatal_connection_cb(fbconn); return; } purple_ssl_input_add(fbconn->ssl_conn, http_post_or_get_ssl_readdata_cb, fbconn); } static void http_host_lookup_cb(GSList *hosts, gpointer data, const char *error_message) { GSList *host_lookup_list; struct sockaddr_in *addr; gchar *hostname; gchar *ip_address = NULL; HttpHandler *handler; PurpleDnsQueryData *query; purple_debug_info("httpproxy", "updating cache of dns addresses\n"); /* Extract variables */ host_lookup_list = data; handler = host_lookup_list->data; host_lookup_list = g_slist_delete_link(host_lookup_list, host_lookup_list); hostname = host_lookup_list->data; host_lookup_list = g_slist_delete_link(host_lookup_list, host_lookup_list); query = host_lookup_list->data; host_lookup_list = g_slist_delete_link(host_lookup_list, host_lookup_list); /* The callback has executed, so we no longer need to keep track of * the original query. This always needs to run when the cb is * executed. */ handler->dns_queries = g_slist_remove(handler->dns_queries, query); /* Any problems, capt'n? */ if (error_message != NULL) { purple_debug_warning("httpproxy", "Error doing host lookup: %s\n", error_message); return; } if (hosts == NULL) { purple_debug_warning("httpproxy", "Could not resolve host name\n"); return; } /* * DNS lookups can return a list of IP addresses, but we only cache * the first one. So free the rest. */ while (hosts != NULL) { /* Discard the length... */ hosts = g_slist_delete_link(hosts, hosts); addr = hosts->data; if(!ip_address && addr->sin_addr.s_addr) ip_address = g_strdup(inet_ntoa(addr->sin_addr)); /* Free the address... */ g_free(hosts->data); hosts = g_slist_delete_link(hosts, hosts); } purple_debug_info("httpproxy", "Host %s has IP %s\n", hostname, ip_address); g_hash_table_insert(handler->hostname_ip_cache, hostname, ip_address); } static void http_cookie_foreach_cb(gchar *cookie_name, gchar *cookie_value, GString *str) { /* TODO: Need to escape name and value? */ g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); } /** * Serialize the handler->cookie_table hash table to a string. */ static gchar *http_cookies_to_string(HttpHandler *handler) { GString *str; str = g_string_new(NULL); g_hash_table_foreach(handler->cookie_table, (GHFunc)http_cookie_foreach_cb, str); return g_string_free(str, FALSE); } static void http_ssl_connection_error(PurpleSslConnection *ssl, PurpleSslErrorType errortype, gpointer data) { HttpConnection *fbconn = data; PurpleConnection *pc = fbconn->handler->pc; fbconn->ssl_conn = NULL; http_connection_destroy(fbconn); purple_connection_ssl_error(pc, errortype); } void http_post_or_get(HttpHandler *handler, HttpMethod method, const gchar *host, const gchar *url, const gchar *postdata, HttpProxyCallbackFunc callback_func, gpointer user_data, gboolean keepalive) { GString *request; gchar *cookies; HttpConnection *fbconn; gchar *real_url; gboolean is_proxy = FALSE; const gchar *user_agent; const gchar* const *languages; gchar *language_names; /* TODO: Fix keepalive and use it as much as possible */ keepalive = FALSE; if (host == NULL) host = "linuxfr.org"; if (handler && handler->account && handler->account->proxy_info && (handler->account->proxy_info->type == PURPLE_PROXY_HTTP || (handler->account->proxy_info->type == PURPLE_PROXY_USE_GLOBAL && purple_global_proxy_get_info() && purple_global_proxy_get_info()->type == PURPLE_PROXY_HTTP))) { real_url = g_strdup_printf("http://%s%s", host, url); is_proxy = TRUE; } else { real_url = g_strdup(url); } cookies = http_cookies_to_string(handler); user_agent = purple_account_get_string(handler->account, "user-agent", "libpurple"); /* Build the request */ request = g_string_new(NULL); g_string_append_printf(request, "%s %s HTTP/1.0\r\n", (method & HTTP_METHOD_POST) ? "POST" : "GET", real_url); g_string_append_printf(request, "Host: %s\r\n", host); g_string_append_printf(request, "Connection: %s\r\n", (keepalive ? "Keep-Alive" : "close")); g_string_append_printf(request, "User-Agent: %s\r\n", user_agent); if (method & HTTP_METHOD_POST) { g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded\r\n"); g_string_append_printf(request, "Content-length: %zu\r\n", strlen(postdata)); } g_string_append_printf(request, "Accept: */*\r\n"); /* linuxfr accepts only connections from that referer. */ g_string_append_printf(request, "Referer: http://%s%s/\r\n", host, real_url); g_string_append_printf(request, "Cookie: %s\r\n", cookies); #ifdef HAVE_ZLIB if (zlib_inflate != NULL) g_string_append_printf(request, "Accept-Encoding: gzip\r\n"); #endif /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */ languages = g_get_language_names(); language_names = g_strjoinv(", ", (gchar **)languages); purple_util_chrreplace(language_names, '_', '-'); g_string_append_printf(request, "Accept-Language: %s\r\n", language_names); g_free(language_names); purple_debug_misc("httpproxy", "sending request headers:\n%s\n", request->str); g_string_append_printf(request, "\r\n"); if (method & HTTP_METHOD_POST) g_string_append_printf(request, "%s", postdata); /* If it needs to go over a SSL connection, we probably shouldn't print * it in the debug log. Without this condition a user's password is * printed in the debug log */ if (method == HTTP_METHOD_POST) purple_debug_misc("httpproxy", "sending request data:\n%s\n", postdata); g_free(cookies); g_free(real_url); /* * Do a separate DNS lookup for the given host name and cache it * for next time. * * TODO: It would be better if we did this before we call * purple_proxy_connect(), so we could re-use the result. * Or even better: Use persistent HTTP connections for servers * that we access continually. * * TODO: This cache of the hostname<-->IP address does not respect * the TTL returned by the DNS server. We should expire things * from the cache after some amount of time. */ if (!is_proxy) { /* Don't do this for proxy connections, since proxies do the DNS lookup */ gchar *host_ip; host_ip = g_hash_table_lookup(handler->hostname_ip_cache, host); if (host_ip != NULL) { purple_debug_info("httpproxy", "swapping original host %s with cached value of %s\n", host, host_ip); host = host_ip; } else if (handler->account && !handler->account->disconnecting) { GSList *host_lookup_list = NULL; PurpleDnsQueryData *query; host_lookup_list = g_slist_prepend( host_lookup_list, g_strdup(host)); host_lookup_list = g_slist_prepend( host_lookup_list, handler); query = purple_dnsquery_a(host, 80, http_host_lookup_cb, host_lookup_list); handler->dns_queries = g_slist_prepend(handler->dns_queries, query); host_lookup_list = g_slist_append(host_lookup_list, query); } } fbconn = g_new0(HttpConnection, 1); fbconn->handler = handler; fbconn->method = method; fbconn->hostname = g_strdup(host); fbconn->request = request; fbconn->callback = callback_func; fbconn->user_data = user_data; fbconn->fd = -1; fbconn->connection_keepalive = keepalive; fbconn->request_time = time(NULL); handler->conns = g_slist_prepend(handler->conns, fbconn); http_attempt_connection(fbconn); } static void http_attempt_connection(HttpConnection *fbconn) { HttpHandler *handler = fbconn->handler; #if 0 /* Connection to attempt retries. This code doesn't work perfectly, but * remains here for future reference if needed */ if (time(NULL) - fbconn->request_time > 5) { /* We've continuously tried to remake this connection for a * bit now. It isn't happening, sadly. Time to die. */ purple_debug_error("httpproxy", "could not connect after retries\n"); http_fatal_connection_cb(fbconn); return; } purple_debug_info("httpproxy", "making connection attempt\n"); /* TODO: If we're retrying the connection, consider clearing the cached * DNS value. This will require some juggling with the hostname param */ /* TODO/FIXME: This retries almost instantenously, which in some cases * runs at blinding speed. Slow it down. */ /* TODO/FIXME: this doesn't retry properly on non-ssl connections */ #endif if (fbconn->method & HTTP_METHOD_SSL) { fbconn->ssl_conn = purple_ssl_connect(handler->account, fbconn->hostname, 443, http_post_or_get_ssl_connect_cb, http_ssl_connection_error, fbconn); } else { fbconn->connect_data = purple_proxy_connect(NULL, handler->account, fbconn->hostname, 80, http_post_or_get_connect_cb, fbconn); } return; } char* http_url_encode(const char *string, int use_plus) { int alloc=strlen(string)+1; char *ns = malloc(alloc); unsigned char in; int newlen = alloc; int index=0; while(*string) { in = *string; if(' ' == in && use_plus) ns[index++] = '+'; else if(!(in >= 'a' && in <= 'z') && !(in >= 'A' && in <= 'Z') && !(in >= '0' && in <= '9')) { /* encode it */ newlen += 2; /* the size grows with two, since this'll become a %XX */ if(newlen > alloc) { alloc *= 2; ns = realloc(ns, alloc); if(!ns) return NULL; } sprintf(&ns[index], "%%%02X", in); index+=3; } else { /* just copy this */ ns[index++]=in; } string++; } ns[index]=0; /* terminate it */ return ns; } minbif-1.0.5+git20120508/plugins/lib/http.h0000644000175000017500000000424511754151773016065 0ustar sebseb/* * libfacebook * * libfacebook is the property of its developers. See the COPYRIGHT file * for more details. * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLUGIN_HTTP_H #define PLUGIN_HTTP_H #include /* * This is a bitmask. */ typedef enum { HTTP_METHOD_GET = 0x0001, HTTP_METHOD_POST = 0x0002, HTTP_METHOD_SSL = 0x0004 } HttpMethod; typedef struct _HttpConnection HttpConnection; typedef struct _HttpHandler HttpHandler; typedef void (*HttpProxyCallbackFunc)(HttpHandler *handler, gchar *data, gsize data_len, gpointer user_data); struct _HttpHandler { PurpleAccount *account; PurpleConnection *pc; GSList *conns; /**< A list of all active HttpConnections */ GSList *dns_queries; GHashTable *cookie_table; GHashTable *hostname_ip_cache; void* data; }; struct _HttpConnection { HttpHandler *handler; HttpMethod method; gchar *hostname; GString *request; HttpProxyCallbackFunc callback; gpointer user_data; char *rx_buf; size_t rx_len; PurpleProxyConnectData *connect_data; PurpleSslConnection *ssl_conn; int fd; guint input_watcher; gboolean connection_keepalive; time_t request_time; }; char* http_url_encode(const char *string, int use_plus); void http_connection_destroy(HttpConnection *fbconn); void http_post_or_get(HttpHandler *handler, HttpMethod method, const gchar *host, const gchar *url, const gchar *postdata, HttpProxyCallbackFunc callback_func, gpointer user_data, gboolean keepalive); HttpHandler* http_handler_new(PurpleAccount* acc, void* data); void http_handler_free(HttpHandler* handler); #endif /* PLUGIN_HTTP_H */ minbif-1.0.5+git20120508/plugins/lib/CMakeLists.txt0000644000175000017500000000020411754151773017464 0ustar sebsebADD_LIBRARY(pluginlib http.c ) TARGET_LINK_LIBRARIES(pluginlib ${PURPLE_LIBRARIES}) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") minbif-1.0.5+git20120508/plugins/coincoin/0000755000175000017500000000000011754151773015763 5ustar sebsebminbif-1.0.5+git20120508/plugins/coincoin/message.c0000644000175000017500000002527011754151773017561 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2011 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "message.h" /* From gcoincoin */ static gchar *strutf8( const gchar *pc, guint uMaxChar ) { gunichar uCode ; guchar b ; gsize uLen ; GString *pString ; guint uChar ; const gchar *pcEnd ; if(( pc == NULL )||( *pc == 0 )) { return NULL ; } if( uMaxChar == 0 ) { uMaxChar = G_MAXUINT ; } uLen = strlen( pc ); pcEnd = &pc[ uLen ]; uChar = 0 ; if( g_utf8_validate( pc, (gssize) uLen, NULL ) ) { const gchar *pcStart = pc ; while(( pc < pcEnd )&&( uChar < uMaxChar )) { pc = g_utf8_next_char(pc); uChar++ ; } return g_strndup( pcStart, pc - pcStart ); } pString = g_string_sized_new( uLen ); while(( pc < pcEnd )&&( uChar < uMaxChar )) { b = (guchar) *pc ; if( b < 128 ) { /* Keep ASCII characters, but remove all control characters * but CR, LF and TAB. */ if(( b > 31 )&&( b != 127 )) { g_string_append_c( pString, b ); } else { switch( b ) { case '\n': case '\r': case '\t': break ; default: b = ' ' ; } g_string_append_c( pString, b ); } pc++ ; } else { uCode = g_utf8_get_char_validated( pc, -1 ); if(( uCode != (gunichar)-1 )&&( uCode != (gunichar)-2 )) { /* Keep a valid UTF-8 character as is */ g_string_append_unichar( pString, uCode ); pc = g_utf8_next_char(pc); } else { /* Consider an invalid byte as an ISO-8859-1 character code. * We get rid of ASCII & ISO-8859-1 control characters. */ if(( b > 0x1F )&&( b < 0x7F )) { /* ASCII characters, excluding control characters */ g_string_append_c( pString, b ); } else if( b > 0x9F ) { /* ISO-8859-1 character, excluding control character (0x7F-0x9F) */ g_string_append_unichar( pString, (gunichar)b ); } else { g_string_append_c( pString, ' ' ); } pc++ ; } } uChar++ ; } #ifdef DEBUG g_assert( g_utf8_validate( pString->str, -1, NULL ) ); #endif return g_string_free( pString, FALSE ); } xmlnode* coincoin_xmlparse(gchar* response, gsize len) { gchar* utf8 = strutf8(response, len); xmlnode* node = xmlnode_from_str(utf8, len); g_free(utf8); return node; } CoinCoinMessage* coincoin_message_new(gint64 id, xmlnode* post) { CoinCoinMessage* msg; xmlnode* message = xmlnode_get_child(post, "message"); xmlnode* info = xmlnode_get_child(post, "info"); xmlnode* login = xmlnode_get_child(post, "login"); gchar *data, *ptr; static struct tm t; time_t tt = time(NULL); if(!message || !info || !login) return NULL; /* Parse time */ if (sscanf(xmlnode_get_attrib(post, "time"), "%4d%2d%2d%2d%2d%2d", &t.tm_year,&t.tm_mon,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec) == 6) { t.tm_year -= 1900; t.tm_mon -= 1; tt = mktime(&t); } /* Skip chars before message. */ ptr = data = xmlnode_get_data(message); while(ptr && (*ptr == '\t' || *ptr == '\n' || *ptr == '\r')) ++ptr; msg = g_new0(CoinCoinMessage, 1); if(!msg) { return NULL; } msg->message = g_strdup(ptr); msg->info = xmlnode_get_data(info); msg->from = xmlnode_get_data(login); msg->timestamp = tt; msg->id = id; msg->ref = 1; msg->multiple = FALSE; g_free(data); return msg; } void coincoin_message_free(CoinCoinMessage* msg) { g_free(msg->message); g_free(msg->info); g_free(msg->from); g_free(msg); } gchar* coincoin_convert_message(CoinCoinAccount* cca, const char* msg) { GString* s; const gchar *start, *next; gchar* _msg = purple_markup_strip_html(msg); if(purple_account_get_bool(cca->account, "no_reformat_messages", FALSE)) return _msg; s = g_string_sized_new(strlen(_msg)); for(start = _msg; *start; start = next) { next = g_utf8_next_char(start); while(*next && *next != ' ') next = g_utf8_next_char(next); if(next > start+2 && *next && next[-1] == ':') { unsigned ref = 1; const gchar *end = start; gchar *nickname; while(*end && *end != ':' && *end != '\xc2') end = g_utf8_next_char(end); nickname = g_strndup(start, end-start); if (*end == ':') ++end; if(*end >= '0' && *end <= '9') { ref = strtoul(end, NULL, 10); } else if(*end == '\xc2') { if(end[1] == '\xb9') ref = 1; // ¹ else if(end[1] == '\xb2') ref = 2; // ² else if(end[1] == '\xb3') ref = 3; // ³ } GSList *m; CoinCoinMessage* cur = NULL; unsigned found = 0; for(m = cca->messages; m; m = m->next) { cur = m->data; if (!strcasecmp(cur->from, nickname) && ++found == ref) break; } g_free(nickname); if(m) { struct tm t; localtime_r(&cur->timestamp, &t); g_string_append_printf(s, "%02d:%02d:%02d", t.tm_hour, t.tm_min, t.tm_sec); if (cur->multiple) g_string_append_printf(s, ":%d", cur->ref); continue; } } if(*next == ' ') next = g_utf8_next_char(next); g_string_append_len(s, start, next-start); } g_free(_msg); return g_string_free(s, FALSE); } static void coincoin_message_ref(CoinCoinMessage* msg, GSList* messages) { GString* s = g_string_sized_new(strlen(msg->message)); gchar *start, *next; struct tm t; localtime_r(&msg->timestamp, &t); for(start = msg->message; *start; start = next) { next = g_utf8_next_char(start); /* totoz */ if(*start == '[' && *(start+1) == ':') { gchar* end = start; while(*end && *end != ']') end = g_utf8_next_char(end); if(*end == ']') { ++end; g_string_append(s, ""); g_string_append_len(s, start, end-start); g_string_append(s, ""); next = end; } else g_string_append_len(s, start, next-start); } /* msg refs */ else if((*start >= '0' && *start <= '9') || *start == ':') { unsigned ref = 1; gboolean ref_no_secs = FALSE; gchar* end = start; gchar* clock; while(*end && ((*end >= '0' && *end <= '9') || *end == ':')) end = g_utf8_next_char(end); /* Detect ¹²³ unicode refs. */ if(*end == '\xc2') { if(end[1] == '\xb9') ref = 1; // ¹ else if(end[1] == '\xb2') ref = 2; // ² else if(end[1] == '\xb3') ref = 3; // ³ } clock = g_strndup(start, end-start); if(sscanf(clock, "%02d:%02d:%02d:%u", &t.tm_hour, &t.tm_min, &t.tm_sec, &ref) == 4 || sscanf(clock, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 || (sscanf(clock, "%02d:%02d", &t.tm_hour, &t.tm_min) == 2 && (ref_no_secs = TRUE))) { GSList* m; struct tm m_t; for(m = messages; m; m = m->next) { CoinCoinMessage* cur = m->data; localtime_r(&cur->timestamp, &m_t); if (t.tm_hour == m_t.tm_hour && t.tm_min == m_t.tm_min && (ref_no_secs || (t.tm_sec == m_t.tm_sec && cur->ref == ref))) break; } if(m) { g_string_append(s, ((CoinCoinMessage*)m->data)->from); g_string_append(s, ": "); } g_string_append(s, ""); g_string_append(s, clock); g_string_append(s, ""); } else g_string_append(s, clock); g_free(clock); next = end; } else g_string_append_len(s, start, next-start); } g_free(msg->message); msg->message = g_string_free(s, FALSE); } void coincoin_parse_message(HttpHandler* handler, gchar* response, gsize len, gpointer userdata) { CoinCoinAccount* cca = handler->data; PurpleConversation* convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, "board", cca->account); if(!convo) return; // not on the board channel xmlnode* node = coincoin_xmlparse(response, len); xmlnode* post; GSList *last_msg = cca->messages; GSList *iter; GSList *messages = NULL; unsigned i; if(!node) { purple_debug(PURPLE_DEBUG_ERROR, "coincoin", "Unable to parse response.\n"); return; } for(post = xmlnode_get_child(node, "post"); post; post = xmlnode_get_next_twin(post)) { CoinCoinMessage* msg; gint64 id = strtoul(xmlnode_get_attrib(post, "id"), NULL, 10); /* Check if this message has already been showed. */ for(iter = last_msg; iter && ((CoinCoinMessage*)iter->data)->id != id; iter = iter->next) ; if(iter) break; msg = coincoin_message_new(id, post); if(!msg) continue; messages = g_slist_prepend(messages, msg); if(strcmp(msg->from, purple_connection_get_display_name(cca->pc))) { PurpleConvChatBuddy* cb = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(convo), msg->from); if(!cb) purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), msg->from, msg->info, PURPLE_CBFLAGS_NONE, FALSE); } } /* Flush messages (in reversed order) */ for(iter = messages; iter; ) { CoinCoinMessage* msg = iter->data; if(!purple_account_get_bool(cca->account, "no_reformat_messages", FALSE)) coincoin_message_ref(msg, cca->messages); serv_got_chat_in(cca->pc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)), msg->from, PURPLE_MESSAGE_DELAYED, msg->message, msg->timestamp); if(cca->messages && ((CoinCoinMessage*)cca->messages->data)->timestamp == msg->timestamp) { msg->multiple = ((CoinCoinMessage*)cca->messages->data)->multiple = TRUE; msg->ref = ((CoinCoinMessage*)cca->messages->data)->ref + 1; } GSList* link = iter; iter = iter->next; link->next = cca->messages; cca->messages = link; } /* Now purge extra-messages */ for(i = 0, iter = last_msg; iter; ++i) { if(i < CC_LAST_MESSAGE_MAX) iter = iter->next; else if(i == CC_LAST_MESSAGE_MAX) { GSList* prev; prev = iter; iter = iter->next; prev->next = NULL; } else { /* This user doesn't participate to conversation * anymore. So it can leave channel. */ CoinCoinMessage* cur = iter->data; if(strcmp(cur->from, purple_connection_get_display_name(cca->pc)) && purple_conv_chat_cb_find(PURPLE_CONV_CHAT(convo), cur->from)) { GSList* it = cca->messages; while(it && it != iter && strcmp(((CoinCoinMessage*)it->data)->from, cur->from)) it = it->next; if(it == iter || !it) purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), cur->from, NULL); } coincoin_message_free(cur); iter->data = NULL; iter = g_slist_delete_link(iter, iter); } } xmlnode_free(node); } minbif-1.0.5+git20120508/plugins/coincoin/coincoin.h0000644000175000017500000000247311754151773017743 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2011 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CC_COINCOIN_H #define CC_COINCOIN_H #include #include "../lib/http.h" #define CC_NAME "libpurple (coincoin)" #define CC_DEFAULT_HOSTNAME "linuxfr.org" #define CC_DEFAULT_BOARD "/board/index.xml" #define CC_DEFAULT_POST "/board" #define CC_DEFAULT_TEMPLATE "board[message]=%s" #define CC_CHECK_INTERVAL 30 typedef struct _CoinCoinAccount CoinCoinAccount; struct _CoinCoinAccount { PurpleAccount *account; PurpleConnection *pc; HttpHandler* http_handler; GSList* messages; guint new_messages_check_timer; gchar* hostname; }; #endif /* CC_COINCOIN_H */ minbif-1.0.5+git20120508/plugins/coincoin/coincoin.c0000644000175000017500000003040311754151773017730 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2011 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "../lib/http.h" #include "message.h" #include "coincoin.h" PurplePlugin *_coincoin_plugin = NULL; static CoinCoinAccount* coincoin_account_new(PurpleAccount *account) { CoinCoinAccount* cca; cca = g_new0(CoinCoinAccount, 1); cca->account = account; cca->pc = purple_account_get_connection(account); cca->http_handler = http_handler_new(cca->account, cca); cca->hostname = NULL; account->gc->proto_data = cca; return cca; } static void coincoin_account_free(CoinCoinAccount* cca) { if (cca->new_messages_check_timer) { purple_timeout_remove(cca->new_messages_check_timer); } while (cca->messages != NULL) { CoinCoinMessage *msg = cca->messages->data; cca->messages = g_slist_remove(cca->messages, msg); coincoin_message_free(msg); } http_handler_free(cca->http_handler); g_free(cca->hostname); g_free(cca); } static const char *coincoin_blist_icon(PurpleAccount *a, PurpleBuddy *b) { return "coincoin"; } static void coincoin_check_new_messages(CoinCoinAccount* cca) { gint flags = HTTP_METHOD_GET; if (purple_account_get_bool(cca->account, "ssl", FALSE)) flags |= HTTP_METHOD_SSL; http_post_or_get(cca->http_handler, flags , cca->hostname, purple_account_get_string(cca->account, "board", CC_DEFAULT_BOARD), NULL, coincoin_parse_message, NULL, FALSE); } static void coincoin_login_cb(HttpHandler *handler, gchar *response, gsize len, gpointer userdata) { CoinCoinAccount* cca = handler->data; purple_connection_update_progress(cca->pc, "Authenticating", 2, 3); xmlnode* node = coincoin_xmlparse(response, len); if(!node || strcmp(node->name, "board")) { purple_connection_error_reason(cca->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "This is not a DaCode board. (check 'board' and 'post' settings)"); } else { purple_connection_set_state(cca->pc, PURPLE_CONNECTED); serv_got_joined_chat(cca->pc, 1, "board"); coincoin_parse_message(cca->http_handler, response, len, userdata); cca->new_messages_check_timer = g_timeout_add_seconds(CC_CHECK_INTERVAL, (GSourceFunc)coincoin_check_new_messages, cca); } if(node) xmlnode_free(node); } static void coincoin_login(PurpleAccount *account) { PurpleConnection *gc; CoinCoinAccount* cca; gint flags = HTTP_METHOD_GET; char **parts, **part; const char *username = purple_account_get_username(account); gc = purple_account_get_connection(account); gc->flags |= PURPLE_CONNECTION_NO_NEWLINES; cca = coincoin_account_new(account); parts = g_strsplit(username, "@", 2); purple_connection_set_display_name(gc, parts[0]); cca->hostname = g_strdup(parts[1]); g_strfreev(parts); /* Error localized in libpurple jabber.c */ if (purple_account_get_bool(account, "ssl", FALSE)) { if(!purple_ssl_is_supported()) { purple_connection_error_reason (purple_account_get_connection(account), PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, "Server requires TLS/SSL for login. No TLS/SSL support found."); return; } else flags |= HTTP_METHOD_SSL; } purple_connection_set_state(gc, PURPLE_CONNECTING); purple_connection_update_progress(gc, "Connecting", 1, 3); g_hash_table_replace(cca->http_handler->cookie_table, g_strdup("login"), g_strdup(purple_connection_get_display_name(gc))); parts = g_strsplit(purple_connection_get_password(gc), ";", -1); for(part = parts; part && *part; ++part) { char** keys = g_strsplit(*part, "=", 2); g_hash_table_replace(cca->http_handler->cookie_table, g_strdup(keys[0]), g_strdup(keys[1])); g_strfreev(keys); } g_strfreev(parts); http_post_or_get(cca->http_handler, flags , cca->hostname, purple_account_get_string(account, "board", CC_DEFAULT_BOARD), NULL, coincoin_login_cb, NULL, FALSE); } static void coincoin_close(PurpleConnection *gc) { g_return_if_fail(gc != NULL); g_return_if_fail(gc->proto_data != NULL); coincoin_account_free(gc->proto_data); } static void coincoin_get_info(PurpleConnection *gc, const char *who) { } static void coincoin_chat_leave (PurpleConnection *gc, int id) { } static void coincoin_message_posted(HttpHandler *handler, gchar *response, gsize len, gpointer userdata) { CoinCoinAccount* cca = handler->data; if(len) purple_debug(PURPLE_DEBUG_ERROR, "coincoin", "Unable to send message to tribune.\n"); else coincoin_check_new_messages(cca); } static int coincoin_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags) { CoinCoinAccount* cca = gc->proto_data; gint http_flags = HTTP_METHOD_POST; if (purple_account_get_bool(cca->account, "ssl", FALSE)) http_flags |= HTTP_METHOD_SSL; gchar* msg = coincoin_convert_message(cca, what); gchar* encoded_msg = http_url_encode(msg, 1); const char* post_template = purple_account_get_string(cca->account, "template", CC_DEFAULT_TEMPLATE); GString* s = g_string_sized_new(strlen(post_template)); const char *start, *next; for(start = post_template; *start; start = next) { next = g_utf8_next_char(start); if (*start == '%' && *(start+1) == 's') { g_string_append(s, encoded_msg); next++; } else g_string_append_len(s, start, next-start); } gchar* postdata = g_string_free(s, FALSE); http_post_or_get(cca->http_handler, http_flags , cca->hostname, purple_account_get_string(cca->account, "post", CC_DEFAULT_POST), postdata, coincoin_message_posted, NULL, FALSE); g_free(postdata); g_free(encoded_msg); g_free(msg); return 0; } static GList *coincoin_status_types(PurpleAccount *account) { PurpleStatusType *type; GList *types = NULL; type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE); types = g_list_append(types, type); type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE); types = g_list_append(types, type); return types; } static void coincoin_set_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *gc = purple_account_get_connection(account); //const char *status_id = purple_status_get_id(status); g_return_if_fail(gc != NULL); if (!purple_status_is_active(status)) return; } static void coincoin_keepalive(PurpleConnection *gc) { } static PurplePluginProtocolInfo prpl_info = { OPT_PROTO_PASSWORD_OPTIONAL, NULL, /* user_splits */ NULL, /* protocol_options */ NO_BUDDY_ICONS, /* icon_spec */ coincoin_blist_icon, /* list_icon */ NULL, /* list_emblems */ NULL, /* status_text */ NULL, /* tooltip_text */ coincoin_status_types, /* away_states */ NULL, /* blist_node_menu */ NULL, /* chat_info */ NULL, /* chat_info_defaults */ coincoin_login, /* login */ coincoin_close, /* close */ NULL, /* send_im */ NULL, /* set_info */ NULL, /* send_typing */ coincoin_get_info, /* get_info */ coincoin_set_status, /* set_status */ NULL, /* set_idle */ NULL, /* change_passwd */ NULL, /* add_buddy */ NULL, /* add_buddies */ NULL, /* remove_buddy */ NULL, /* remove_buddies */ NULL, /* add_permit */ NULL, /* add_deny */ NULL, /* rem_permit */ NULL, /* rem_deny */ NULL, /* set_permit_deny */ NULL, /* join_chat */ NULL, /* reject_chat */ NULL, /* get_chat_name */ NULL, /* chat_invite */ coincoin_chat_leave, /* chat_leave */ NULL, /* chat_whisper */ coincoin_chat_send, /* chat_send */ coincoin_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */ NULL, /* buddy_free */ NULL, /* convo_closed */ purple_normalize_nocase, /* normalize */ NULL, /* set_buddy_icon */ NULL, /* remove_group */ NULL, /* get_cb_real_name */ NULL, /* set_chat_topic */ NULL, /* find_blist_chat */ NULL, /* roomlist_get_list */ NULL, /* roomlist_cancel */ NULL, /* roomlist_expand_category */ NULL, /* can_receive_file */ NULL, /* send_file */ NULL, /* new_xfer */ NULL, /* offline_message */ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* get_attention_types */ sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL, /* get_account_text_table */ NULL, /* initiate_media */ NULL /* can_do_media */ #if (PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 7) , NULL, /* get_moods */ NULL, /* set_public_alias */ NULL /* get_public_alias */ #endif #if (PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 8) , NULL, /* add_buddy_with_invite */ NULL /* add_budies_with_invite */ #endif }; static gboolean load_plugin (PurplePlugin *plugin) { return TRUE; } static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_PROTOCOL, /**< type */ NULL, /**< ui_requirement */ 0, /**< flags */ NULL, /**< dependencies */ PURPLE_PRIORITY_DEFAULT, /**< priority */ "prpl-coincoin", /**< id */ "CoinCoin", /**< name */ "1.0", /**< version */ "coincoin Protocol Plugin", /** summary */ "The Coincoin Protocol", /** description */ "Romain Bignon", /**< author */ "http://minbif.im/CoinCoin", /**< homepage */ load_plugin, /**< load */ NULL, /**< unload */ NULL, /**< destroy */ NULL, /**< ui_info */ &prpl_info, /**< extra_info */ NULL, /**< prefs_info */ NULL, /* padding */ NULL, NULL, NULL, NULL }; static void _init_plugin(PurplePlugin *plugin) { PurpleAccountOption *option; PurpleAccountUserSplit *split; GHashTable *ui_info = purple_core_get_ui_info(); const gchar *ui_name = g_hash_table_lookup(ui_info, "version"); if(!ui_name) ui_name = CC_NAME; split = purple_account_user_split_new("Server", CC_DEFAULT_HOSTNAME, '@'); prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); option = purple_account_option_string_new("User-agent", "user-agent", ui_name); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_string_new("Board path", "board", CC_DEFAULT_BOARD); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_string_new("Post path", "post", CC_DEFAULT_POST); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_string_new("POST template", "template", CC_DEFAULT_TEMPLATE); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_bool_new("Use SSL", "ssl", FALSE); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_bool_new("No reformat messages", "no_reformat_messages", FALSE); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); _coincoin_plugin = plugin; } PURPLE_INIT_PLUGIN(coincoin, _init_plugin, info); minbif-1.0.5+git20120508/plugins/coincoin/CMakeLists.txt0000644000175000017500000000025411754151773020524 0ustar sebsebADD_LIBRARY(coincoin SHARED coincoin.c message.c ) TARGET_LINK_LIBRARIES(coincoin ${PURPLE_LIBRARIES} pluginlib) INSTALL(TARGETS coincoin DESTINATION lib/purple-2) minbif-1.0.5+git20120508/plugins/coincoin/README0000644000175000017500000000513211754151773016644 0ustar sebseb LibPurple's CoinCoin Plugin *************************** 1. What is Coincoin? This is a plugin for the purple library, which allow to connect to a DaCode board, to read and send messages on it. The default board used by prpl-coincoin is linuxfr.org/board, which popularized this kind of chat among a few french communities. Other softwares support this kind of board, like Templeet or Da Portative Bouchot. You can find a (non-exhaustive) list of boards on this page (look at the "tribune" links): http://chrisix.free.fr/pycoincoin/config/list.php A board has an unique chat space, so prpl-coincoin creates at connection an unique channel "board". 2. How to install it Compile prpl-coincoin and copy the libcoincoin.so file to /usr/lib/purple-2/. Then, use your favorite purple client (for example MinBif), and the module is automatically loaded. 3. How to use it Create a new account, and select the "CoinCoin" plugin. Then, you need to give this settings: * Username: your login[@server] on board (if the @server part is not given, defauts to linuxfr.org board) * Password: locate your cookies for the website (from your browser), and put them here, separated by semi-colons (;) Full example for a custom Da Portative Bouchot board: /MAP ADD coincoin ToTo@tribioune.mydomain.tld -password md5=xxxxxxxxxxxxxxxxxxxxxxxx;unique_id=yyyyyyyyyyyyyyyyyyyyyyy -board /remote.xml -post /index.php/add 4. You're connected The *status* channel is always created in minbif, but always empty for coincoin accounts, as this system as no presence information. The *board* channel is automatically created, and you can chat! 5. Help for readability Each totoz is colored in green, and clocks are colored in blue. 6. Clocks Your purple client, unlike other coincoin clients, doesn't hilight referenced message when you put your mouse on a timestamp. To increase readability, prpl-coincoin adds the sender nickname of the referenced message before the timestamp For example, if there are two messages with timestamp: 10:23:09 hey guys 10:28:43 10:23:09 f0ck you the last message is replaced with: 10:28:43 blah: 10:23:09 f0ck you Also, when you write a message, to reference to a previous message, instead of putting the timestamp, you can use the nickname, eventually suffixed with the number of message from last. For example: 10:23:09 hey guys 10:28:43 10:23:09 f0ck you 10:30:21 hey nigger If you write: 10:32:33 blah:2: youhou ho¹: you're bad The message sent is: 10:32:33 10:23:03 youhou 10:28:43 you're bad Have fun. minbif-1.0.5+git20120508/plugins/coincoin/message.h0000644000175000017500000000250711754151773017564 0ustar sebseb/* * Minbif - IRC instant messaging gateway * Copyright(C) 2009-2011 Romain Bignon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CC_MESSAGE_H #define CC_MESSAGE_H #include "coincoin.h" #define CC_LAST_MESSAGE_MAX 100 typedef struct _CoinCoinMessage CoinCoinMessage; struct _CoinCoinMessage { gchar* message; gchar* info; gchar* from; time_t timestamp; unsigned ref; gboolean multiple; gint64 id; }; gchar* coincoin_convert_message(CoinCoinAccount* cca, const char* msg); void coincoin_parse_message(HttpHandler* handler, gchar* response, gsize len, gpointer userdata); xmlnode* coincoin_xmlparse(gchar* response, gsize len); void coincoin_message_free(CoinCoinMessage* msg); #endif /* CC_MESSAGE_H */ minbif-1.0.5+git20120508/plugins/CMakeLists.txt0000644000175000017500000000052611754151773016725 0ustar sebsebadd_subdirectory(lib) OPTION(ENABLE_PLUGIN_COINCOIN "Enable coincoin plugin build" ON) IF(ENABLE_PLUGIN_COINCOIN) add_subdirectory(coincoin) ENDIF(ENABLE_PLUGIN_COINCOIN) OPTION(ENABLE_PLUGIN_GAYATTITUDE "Enable coincoin plugin build" ON) IF(ENABLE_PLUGIN_GAYATTITUDE) add_subdirectory(gayattitude) ENDIF(ENABLE_PLUGIN_GAYATTITUDE) minbif-1.0.5+git20120508/man/0000755000175000017500000000000011754151773013254 5ustar sebsebminbif-1.0.5+git20120508/man/minbif.80000644000175000017500000000507111754151773014614 0ustar sebseb.TH MINBIF 8 "04 December 2011" .SH NAME minbif \- The IRC instant messaging gateway .SH SYNOPSIS .hy 0 \fBminbif [\-hv] [\-\-pidfile \fIpidfile\fB]\fP .I CONFIG_FILE .SH DESCRIPTION .LP Minbif is an IRC gateway to IM networks which provides the following features: .nf * Minbif uses a library which abstracts all IM calls, and has several plugins to support more than 15 IM protocols (IRC included!); * Two modes: inetd and daemon fork; * Only IRC commands are used to control Minbif; * IM Certificates check; * Buddies are IRC users; * Each account is associated to a status channel, where all connected buddies are; * Add and remove buddies from list with /INVITE and /KICK commands; * Blocked users are bans on the account's status channel; * Display when a buddy is typing a message; * Can chat with someone who is not in your buddy list; * You can see buddies' icons (with libcaca) or download them; * DCC SEND an image to set your icon on IM networks; * Display extended information about buddies with /WII command; * Status management; * Can send and receive files, which are sent or received to/from your IRC client with DCC SEND; * Conversation channels are supported; * Auto\-rejoin conversation channels at connection; * Display list of channels on an IM account with /LIST; * irssi scripts to increase your user experience of minbif; * CACAcam (webcam in ascii art); * CoinCoin plugin for libpurple; * PAM support; * TLS support with certificate auth. .fi .SH OPTIONS .TP \fB\-\-pidfile\fR \fIpidfile\fR path to pidfile where the minbif's PID is written. .TP \fB\-\-version\fR display version of minbif. .TP \fB\-\-help\fR show help. .TP .B config_file Configuration file location. .SH CONFIGURATION Edit the \fIminbif.conf\fP file and change value when needed. With the inetd mode, use this command to add minbif: .nf # update\-inetd \-\-add '56667 stream tcp nowait minbif /usr/sbin/tcpd /usr/bin/minbif /etc/minbif/minbif.conf' .fi With the daemon fork mode, run minbif with: .nf # minbif /etc/minbif/minbif.conf .fi .SH HOW TO USE Connect your IRC client (for examble \fIirssi\fP) on minbif with this command: .nf /server localhost 56667 \fIpassword\fP .fi The nickname set on your IRC client is your username on minbif. First time you connect to minbif with this nickname, the account is created and the \fIpassword\fP parameter is set. Next, use the \fB/map help\fP command to know how to add a network. See also: http://minbif.im/Quick_start .SH COPYRIGHT Copyright(C) 2009-2011 Romain Bignon .LP For full COPYRIGHT see COPYING file with minbif package. .LP .RE .SH FILES "minbif.conf" minbif-1.0.5+git20120508/CMakeLists.txt0000644000175000017500000001122311754151773015240 0ustar sebsebcmake_minimum_required(VERSION 2.6) PROJECT(minbif) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules ${CMAKE_MODULE_PATH} ) INCLUDE(FindPkgConfig) INCLUDE(CheckIncludeFiles) INCLUDE(CheckLibraryExists) if (DEBUG) message(STATUS "Configuring for debug") ADD_DEFINITIONS(-DDEBUG -Werror) SET(CMAKE_BUILD_TYPE Debug) else (DEBUG) message(STATUS "Configuring for release") SET(CMAKE_BUILD_TYPE Release) endif (DEBUG) PKG_CHECK_MODULES(PURPLE REQUIRED purple>=2.5) IF(NOT PURPLE_FOUND) MESSAGE(FATAL_ERROR "Please install the purple library (version >=2.5).") ENDIF(NOT PURPLE_FOUND) OPTION(ENABLE_MINBIF "Enable minbif compilation" ON) IF(ENABLE_MINBIF) PKG_CHECK_MODULES(GTHREAD REQUIRED gthread-2.0) OPTION(ENABLE_IMLIB "Use imlib library" ON) IF(ENABLE_IMLIB) PKG_CHECK_MODULES(IMLIB imlib2>=1.0) IF(IMLIB_FOUND) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_IMLIB") INCLUDE(FindX11) IF(NOT X11_FOUND) ADD_DEFINITIONS(-DX_DISPLAY_MISSING) ENDIF(NOT X11_FOUND) ELSE(IMLIB_FOUND) MESSAGE(FATAL_ERROR "Unable to find the imlib2 library. To disable use of imlib2, run 'make ENABLE_IMLIB=OFF'") ENDIF(IMLIB_FOUND) ENDIF(ENABLE_IMLIB) OPTION(ENABLE_CACA "Enable libcaca library" ON) IF(ENABLE_CACA) IF(NOT ENABLE_IMLIB) MESSAGE(FATAL_ERROR "You need to enable IMLIB to enable CACA") ENDIF(NOT ENABLE_IMLIB) PKG_CHECK_MODULES(CACA caca>=0.99) IF(CACA_FOUND) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_CACA") ELSE(CACA_FOUND) MESSAGE(FATAL_ERROR "Unable to find the libcaca library. To disable caca output, run 'make ENABLE_CACA=OFF'") ENDIF(CACA_FOUND) PKG_CHECK_MODULES(NEWCACA caca>=0.99.beta17) IF(NOT NEWCACA_FOUND) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_OLD_CACA") ENDIF(NOT NEWCACA_FOUND) ENDIF(ENABLE_CACA) OPTION(ENABLE_VIDEO "Enable video support" ON) IF(ENABLE_VIDEO) IF(NOT ENABLE_CACA) MESSAGE(FATAL_ERROR "You need to enable CACA to enable VIDEO") ENDIF(NOT ENABLE_CACA) PKG_CHECK_MODULES(GSTREAMER gstreamer-0.10>=0.10.10 gstreamer-interfaces-0.10>=0.10) PKG_CHECK_MODULES(FARSIGHT farsight2-0.10>=0.0.9) PKG_CHECK_MODULES(PURPLE_26 purple>=2.6.1) IF(GSTREAMER_FOUND AND PURPLE_26_FOUND AND FARSIGHT_FOUND) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_VIDEO") ELSE(GSTREAMER_FOUND AND PURPLE_26_FOUND AND FARSIGHT_FOUND) MESSAGE(FATAL_ERROR "Dependencies for video were not met. Install gstreamer, farsight and libpurple>=2.6 first. Or to disable video, run 'make ENABLE_VIDEO=0'") ENDIF(GSTREAMER_FOUND AND PURPLE_26_FOUND AND FARSIGHT_FOUND) ENDIF(ENABLE_VIDEO) OPTION(ENABLE_PAM "Enable PAM support" ON) IF (ENABLE_PAM) find_package(PAM) IF (PAM_FOUND) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_PAM") ELSE (PAM_FOUND) MESSAGE(FATAL_ERROR "Dependencies for PAM were not met. Install libpam-dev. Or to disable PAM support, run 'make ENABLE_PAM=0'") ENDIF (PAM_FOUND) ENDIF (ENABLE_PAM) OPTION(ENABLE_TLS "Enable TLS support" ON) IF (ENABLE_TLS) PKG_CHECK_MODULES(GNUTLS REQUIRED "gnutls") IF (GNUTLS_FOUND) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_TLS") ENDIF (GNUTLS_FOUND) ENDIF (ENABLE_TLS) SET(CONF_NAME minbif.conf) SET(MOTD_NAME minbif.motd) IF(NOT MAN_PREFIX) SET(MAN_PREFIX ${CMAKE_INSTALL_PREFIX}/share/man/man8/) ENDIF(NOT MAN_PREFIX) IF(NOT CONF_PREFIX) SET(CONF_PREFIX ${CMAKE_INSTALL_PREFIX}/etc/minbif) ENDIF(NOT CONF_PREFIX) IF(NOT DOC_PREFIX) SET(DOC_PREFIX ${CMAKE_INSTALL_PREFIX}/share/doc/minbif) ENDIF(NOT DOC_PREFIX) INSTALL(FILES man/minbif.8 DESTINATION ${MAN_PREFIX}) INSTALL(FILES COPYING README DESTINATION ${DOC_PREFIX}) INSTALL(DIRECTORY doc/ DESTINATION ${DOC_PREFIX}) INSTALL(FILES ${CONF_NAME} ${MOTD_NAME} DESTINATION ${CONF_PREFIX}) ENDIF(ENABLE_MINBIF) OPTION(ENABLE_PLUGIN "Enable plugin build" OFF) IF(ENABLE_PLUGIN) PKG_CHECK_MODULES(LIBXML REQUIRED libxml-2.0>=2.5) ENDIF(ENABLE_PLUGIN) INCLUDE_DIRECTORIES(${PURPLE_INCLUDE_DIRS} ${GTHREAD_INCLUDE_DIRS} ${CACA_INCLUDE_DIRS} ${IMLIB_INCLUDE_DIRS} ${GSTREAMER_INCLUDE_DIRS} ${FARSIGHT_INCLUDE_DIRS} ${LIBXML_INCLUDE_DIRS} ${PAM_INCLUDE_DIRS} ${GNUTLS_INCLUDE_DIRS} "src/") LINK_DIRECTORIES(${PURPLE_LIBRARY_DIRS} ${GTHREAD_LIBRARY_DIRS} ${CACA_LIBRARY_DIRS} ${IMLIB_LIBRARY_DIRS} ${GSTREAMER_LIBRARY_DIRS} ${FARSIGHT_LIBRARY_DIRS} ${LIBXML_LIBRARY_DIRS} ${GNUTLS_LIBRARY_DIRS}) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_REENTRANT -D_FILE_OFFSET_BITS=64 -Wall -Wextra -Wno-unused-parameter") SET(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS}) IF(ENABLE_MINBIF) add_subdirectory(src) ENDIF(ENABLE_MINBIF) IF(ENABLE_PLUGIN) add_subdirectory(plugins) ENDIF(ENABLE_PLUGIN) MESSAGE(STATUS "Using compiler ${CMAKE_CXX_COMPILER}") MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") minbif-1.0.5+git20120508/Makefile.options.defaults0000644000175000017500000000137411754151773017446 0ustar sebseb# Options for minbif # # Do NOT edit this file directly ! Copy it to Makefile.options.local # and edit it. # # This file is used as an abstraction around CMake command line # Enable debug mde DEBUG ?= OFF # Compile minbif ENABLE_MINBIF ?= ON # Compile minbif with imlib ENABLE_IMLIB ?= ON # Compile with the libcaca support ENABLE_CACA ?= ON # Compile with the video support ENABLE_VIDEO ?= OFF # Compile libpurple plugins ENABLE_PLUGIN ?= OFF # Compile with the pam support ENABLE_PAM ?= ON # Compile with the tls support ENABLE_TLS ?= ON # Installation prefix # PREFIX = /usr/local/ # MAN_PREFIX = /usr/local/share/man/man8/ # CONF_PREFIX = /etc/minbif/ # DOC_PREFIX = /usr/local/share/doc/minbif/ # Print the gcc call line. CMAKE_VERBOSE_MAKEFILE = 0 minbif-1.0.5+git20120508/minbif.conf0000644000175000017500000001107011754152000014573 0ustar sebseb# Path settings path { # Users directory, where minbif will write # every settings. users = /var/lib/minbif/users # Path to motd file. motd = /etc/minbif/minbif.motd } irc { # IRC server hostname. If not set, it'll be automatically detected. #hostname = localhost.localdomain # Minbif mode. # # 0: inetd # 1: daemon (isn't implemented yet) # 2: daemon fork type = 2 # With 'inetd' modes, set some parameters inetd { # Connection security mode # none/tls/starttls/starttls-mandatory #security = none # TLS parameters (if enabled) #tls { # cert_file = /etc/minbif/server.crt # key_file = /etc/minbif/server.key # priority = PERFORMANCE # # # client certificate validation # trust_file = /etc/ssl/certs/ca.crt # crl_file = /etc/ssl/certs/ca.crl #} } # With 'daemon' and 'daemon fork' modes, set some # parameters to listen on network. daemon { # Interface or IP address to listen on. It can be a IPv4 or # a IPv6 address or netmask. # To listen on every interfaces, set 'bind' to '::'. bind = 0.0.0.0 # Port to listen on. port = 6667 # If this parameter is enabled, it run MinBif as a daemon. # stdin, stdout and stderr will be also closed. background = true # Maximum simultaneous connections maxcon = 10 # Connection security mode # none/tls/starttls/starttls-mandatory #security = none # TLS parameters (if enabled) #tls { # cert_file = /etc/minbif/server.crt # key_file = /etc/minbif/server.key # priority = PERFORMANCE # # # client certificate validation # trust_file = /etc/ssl/certs/ca.crt # crl_file = /etc/ssl/certs/ca.crl #} } # Ping interval in seconds. ping = 60 # When a user /WHOIS a buddy, if libcaca is present, the buddy's icon # is displayed in colored ASCII. # You can also setup a web server or whatever you want, and put here # the base of url shown in /WHOIS reply. # # You can for example create a virtual host on Apache, and redirect it # to the folder which contains minbif's users. # Warning: do not forgot to deny access to everything else than .png files! # # buddy_icons_url = http://mydomain.tld/minbif/ # buddy_icons_url = file:///var/lib/minbif/users/ # IRC Operators can rehash configuration, send WALLOPS to other # minbif's users (in daemon fork mode), etc. # # Use the '/OPER login password' to authenticate as an IRC oper. # # Define a block for each Operator: # # oper { # login = romain # password = pupuce # email = romain@minbif.im # } # # oper { # login = pankkake # password = littledick # email = pankkake@blowjob.org # } # # Note that the email address is displayed in /stats o. # Password to protect the server. # # *NOTE* THIS IS AN OPTIONAL SETTING. IF YOU DON'T RUN A PUBLIC # SERVER IT IS NOT NECESSARY. # # If enabled, use it to connect to server first time with: # /server localhost 6667 globalpasswd # Then, change your password with: # /admin password mypasswd # #password = minbifrocks } # Authentication, Authorization and Accounting aaa { # Enable local user database (defaults to true) #use_local = true # Enable PAM authentication/authorization (need the ENABLE_PAM compile flag) #use_pam = false # Child process setuid with the pam user (needs root and pam auth) #pam_setuid = false # Enable connection information for authentication/authorization # (currently only used with TLS client certificates) #use_connection = false } file_transfers { # Enable file transfers feature. enabled = true # Send files to IRC user with DCC, and accept to receive # file from user to send to IM. dcc = true # Port range to listen for DCC. port_range = 1024-65535 # Force minbif to always send DCC requests from a particular IP address. # This is *NOT* the bind address. # # When not set, it tries to guess your public IP address. # dcc_own_ip = 127.0.0.1 } # Log function logging { # What you want to log # DEBUG :Debug information (discouraged) # PARSE :Parse information (discouraged) # PURPLE :Purple warning messages # DESYNCH :Desynchronization # WARNING :Warnings # ERR :Errors # INFO :Information # ALL :Show all infos # You can put several logging level on the same line, separated by a space level = INFO ERR WARNING DESYNCH # Wether to log errors and warning to syslog to_syslog = true # Enable conversation logging # We consider that it's the IRC client job, but if you use the same # purple directory with an other purple client, you'd want to keep # logs at the same place. conv_logs = false } minbif-1.0.5+git20120508/Makefile.parser0000644000175000017500000000147111754152001015420 0ustar sebsebEXTRA_CMAKE_FLAGS += -DDEBUG=$(DEBUG) EXTRA_CMAKE_FLAGS += -DENABLE_MINBIF=$(ENABLE_MINBIF) EXTRA_CMAKE_FLAGS += -DENABLE_IMLIB=$(ENABLE_IMLIB) EXTRA_CMAKE_FLAGS += -DENABLE_CACA=$(ENABLE_CACA) EXTRA_CMAKE_FLAGS += -DENABLE_VIDEO=$(ENABLE_VIDEO) EXTRA_CMAKE_FLAGS += -DENABLE_PLUGIN=$(ENABLE_PLUGIN) EXTRA_CMAKE_FLAGS += -DENABLE_PAM=$(ENABLE_PAM) EXTRA_CMAKE_FLAGS += -DENABLE_TLS=$(ENABLE_TLS) ifneq ($(PREFIX),) CMAKE_PREFIX = -DCMAKE_INSTALL_PREFIX="$(PREFIX)" endif ifneq ($(MAN_PREFIX),) EXTRA_CMAKE_FLAGS += -DMAN_PREFIX=$(MAN_PREFIX) endif ifneq ($(CONF_PREFIX),) EXTRA_CMAKE_FLAGS += -DCONF_PREFIX=$(CONF_PREFIX) endif ifneq ($(DOC_PREFIX),) EXTRA_CMAKE_FLAGS += -DDOC_PREFIX=$(DOC_PREFIX) endif ifneq ($(CMAKE_VERBOSE_MAKEFILE),) EXTRA_CMAKE_FLAGS += -DCMAKE_VERBOSE_MAKEFILE=$(CMAKE_VERBOSE_MAKEFILE) endif minbif-1.0.5+git20120508/valgrind-suppressions0000644000175000017500000000070511754152001016770 0ustar sebseb{ LIBPURPLE Memcheck:Leak ... fun:purple_core_init ... } { GSTREAMER Memcheck:Leak ... fun:g_thread_init_glib ... } { GTHREADS Memcheck:Leak ... fun:gst_init_check ... } { PLUGIN_INIT Memcheck:Leak ... fun:purple_init_plugin ... } { PLUGIN_NETWORK_INIT Memcheck:Leak ... fun:purple_network_init ... } { PLUGIN_SSL_INIT Memcheck:Leak ... fun:purple_ssl_init ... } minbif-1.0.5+git20120508/ChangeLog0000644000175000017500000002470311754152001014242 0ustar sebsebMinbif 1.0.5 (2011-12-04) * Add an option 'file_transfers/dcc_own_ip' in config file. * Change weechat's script to ignore the 'request' nick. * When a new request is received during process of an other one, tell user. * Add a facebook rename script for weechat. * Change users of child process with pam authentification. * Escape URL entities in /JOIN params. * Add a /SCONNECT alias to /CONNECT. * Can use wildcards to match buddies with /WHO. * prpl-coincoin: support hh:mm:ss:ref norloge syntax. * prpl-coincoin: works with the new version of the DLFP board. * Fix: prevent crash with the msn plugin. * Fix: conversion to IRC escape chars. * Fix: compatibility with libpurple 2.8+. Minbif 1.0.4 (2010-09-20) * libpurple 2.4 isn't supported anymore. * /WHO can take -s to display status instead of realname. * Support multilines messages by waiting a delay before sending buffer (#195). * Send RPL_MYINFO and RPL_ISUPPORT at connection to be RFC 2812 compliant. * Only /SVSNICK sends aliases server side. * New command '/STATS p' to manage libpurple plugins. * /MAP commands have to match the start of the exact name. * With '/MAP add', decode usernames as URL-encoded (#338). * Do not add '0' at the end of the new account ID when it's the first one. * Allow joining a remote channel even if account is disconnected (#387). * New option '-m' to binary to select the server mode to create. * Add im_typing_notice script for weechat. * prpl-coincoin new features: support clocks without seconds, can refer to a previous message by writing the nickname, etc. * Fix: crash when which occurs sometimes when disconnecting from an account. * Fix: don't warn about aborted joins if the account is not connected while removing it (#337). * Fix: issues with multilines received messages (#331). * Fix: use unicode in status channel topics. * Fix: ability to unset status. * Fix: file descriptor leak in daemon fork mode. * Fix: infinite loop when there are more than 2 accounts without any ID set. Minbif 1.0.3 (2010-05-15) * Use /topic on status channels to change your status message (#210). * With field requests, display image fields with libcaca. * Improvement of bans management (match wildcards, fix parsing, etc.). * When starting to chat with a buddy in a status channel, every received messages from this buddy are displayed in the status channel (#179). * Do not allow two IRC clients to be logged at the same time on the same account (only with the daemon fork mode) (#178). * Display the current status of a buddy in /WHOIS when he is not away. * Default value of 'server_alias' is now 'true'. * Works with libpurple 2.7.0. * Fix: crash with conversations (occured when a buddy sends a message, then disconnects/reconnects and re-send a message) (#258). * Fix: crash when closing a request. * Fix: compilation on FreeBSD (now using a buildbot slave on this OS) (#255). * Fix: /svsnick collision detection. * Fix: crash when nobody connects to DCC server. * Fix: crash when a buddy has an icon without any path (wtf). Minbif 1.0.2 (2010-04-05) * TLS support. * New 'maxcon' parameter to limit simultaneous connections. * /TOPIC on a status channel change your status on attached accounts. * Can put '%XX' in remote channel names to join, to insert special characters (for example spaces). * Auto-reconnect on accounts only when disconnection was because of a network error. * Display account ID and more information in requests. * Aibility to not store password on an account (it is requested at connection). * Aibility to edit password with '/MAP edit'. * Use imlib2 to convert received buddyicons to right encoded images. * Send several PRIVMSG or NOTICE when there are \n in message. * Better display of /MAP replies (with stats on accounts). * /ADMIN voiced_buddies: enable or disable voices on status channels. * /ADMIN accept_nobuddies_messages: if disabled, messages from unknown buddies are not displayed (prevent spam). * /ADMIN server_aliases: enable or disable sending of aliases server-side. * 'server_aliases' accounts parameter to enable or disable sending of aliases server-side on a specific account. * Command '/STATS u' to display server uptime. * Command '/STATS o' to display every available minbif administrator and their email address. * Command '/INFO' to display copyright information. * Irssi script to auto-rename facebook buddies. * Fix: pam default configuration. * Fix: chat buddy's nickname when users have a jabber resource. * Fix: crash with '/STATS c' on a disconnected account. * Fix: compilation on strange compilers. * Fix: typing notices were broken. * Fix: crash when removing an account which have an active request. * Fix: prevent empty nicknames. * Fix: sent buddy icons cut on the bottom. Minbif 1.0.1 (2010-01-16) * PAM authentication. * /MAP CMD command to run a specific command on an account, which allow you to change nickname, mood messages, etc. * Support fields requests. * Display group name in IRC user realname. * The /MAP REGISTER command registers account on server before adding it. * Conf option to enable the purple conv logging. * /CMD command to send commands in a conversation * Display date before delayed messages if they hasn't been sent today. * Fix: daemonize correctly. * Fix: security issue when a global password is set on the minbif server, it creates the userdir before checking if the password is correct. * Fix: buddy's realnames wasn't updated in realtime. * Fix: the PONG reply wasn't RFC compliant. * Fix: crash in NAMES and TOPIC commands when giving an invalid chan name. * Fix: don't typing notice unless either bitlbee_send_typing or typing_notice is true. Minbif 1.0 (2009-11-18) * Global proxy settings with the /ADMIN command. * Ability to set parameters when joining a conversation channel. * /STATS c: display all available parameters of a specific account. * Transliteration of buddies' IRC nicknames. * Handle mIRC format chars when sending a message. * gayattitude: new prpl-plugin. * coincoin: improvment. * accounts: fix auto-reconnection. * Fix: remove a status channel when there isn't any associated account. * Fix: fix crash when rehashing twice. * Fix: memory leaks. Minbif 1.0-rc2 (2009-10-28) * Timestamps of delayed messages are bold formatted. * coincoin: fix bad-encoded xml files. * coincoin: display nickname of message sender before timestamps. Minbif 1.0-rc (2009-10-24) * Auto-rejoin channels when disconnect/reconnect to an IM account, or when restart minbif. * Channel users status changes are propagated to IRC user. * /TOPIC command to change the conversation channel topic. * Aibility to chat with a channel member. * When an unknown buddy tries to send a message, create a temporary IRC user to chat with him anyway. * /ADMIN log_level: change the log level. * can change ID and status channel of an account with /MAP EDIT. * /LIST : display list of channels for an account. * MSN protocol: use /JOIN #nickname:accid to create a group chat with a buddy. * prpl-coincoin: plugin to connect to a DaCode board. * Reorganization of CMakeLists.txt files. * Fix: Use libcaca 'irc' output for buddy icons. * Fix: Correctly send a ERR_NOSUCHCHANNEL reply when a channel can't be joined. * Fix: TYPING: send stale state to avoid the irssi's plugin to timeout. * Fix: crash when a notify field is NULL. Minbif 1.0-beta2 (2009-10-10) * CACAcam works. * scripts/irssi/im_cacacam.pl: irssi script to display CACAcam in the buddy's query. * Files send supported (/DCC SEND). * Set accounts icon with /DCC SEND buddyicon. * Daemon-fork mode supports IPv6. * Display a MOTD at connection, or with the /MOTD command. * Parameter irc/buddy_icons_url to display an URL to download icon in /WHOIS. * /ADMIN away_idle (bool): enable/disable away when idling. * /SVSNICK: reject invalid nicks. * /MAP edit: edit an account. * /OPER: authenticate as an IRC operator. * /WALLOPS: oper command to send a wallops message to every launched minbif. * /DIE: kill every minbif instances. * /REHASH: reload configuration of every launched minbif. * Handle SIGHUP signal to reload configuration file. * IPC channel in daemon fork mode, to broadcast several commands. * Wrote tests system. * A buildbot is setup on http://buildbot.symlink.me * Fix: join MSN channels when invited (it's still buggous, but doesn't crash anymore). * Fix: memory leak with CACAImage. * Fix: crash when an alias is overrided by server. * Fix: iso-8859-15 charset crash. Minbif 1.0-beta (2009-09-28) * Daemon fork mode. * Ability to protect the minbif server with a password. * File transfers support for reception. * Buggy implementation of CACAcam (do not use it!). * /SVSNICK command to change a buddy's nickname. * /WHO can match on a server name or an account name. * /NAMES command. * /MAP ADD without any channel name will use the default &minbif channel. * /AWAY command. * '/STATS a' to display every available away status. * /WHOIS displays the fully away message. * /WII displays a larger icon and several information about buddy. * /ADMIN command to display and change some minbif account parameters. * Correctly handle the /ME command. * Can leave status channels. * Display notify messages. * Auto-reconnect when disconnected by server. * Auto-rejoin remote channels when reconnecting to minbif. * Display remote channel topics. * Display remote channel history at join (if supported by protocol). * libpurple error messages are logged with the PURPLE loglevel. * Handle SIGTERM. * Support CTCP TYPING. * Imported some bitlbee's irssi scripts and changed them to support minbif. * Added a manpage. * Fix: crash when a remote channel member leaves. * Fix: crash when a remote channel member changes his nickname. * Fix: hilight messages were not displayed. * Fix: correctly handle when a user tries to add me in his buddy list. Minbif 1.0-alpha (2009-05-10) * First revision of project * Minbif uses a library which abstracts all IM calls, and has several plugins to support a lot of IM protocols (IRC included!). * Only IRC commands are used to command Minbif. * Buddies are IRC users. * Each account has a status channel. You see in all connected buddies, and their status on it. * Add and remove buddies from list with /INVITE and /KICK commands. * Blocked users are bans on the account's status channel. * You can see buddies' icons (with libcaca) or download them. * Conversation chats are supported. minbif-1.0.5+git20120508/README0000644000175000017500000000503711754152001013347 0ustar sebseb Minbif - IRC instant messaging gateway ************************************** 1. Abstract =========== Minbif aims to use the libpurple library from the Pidgin project to provide an IRC-friendly instant messaging client. 2. Installation =============== You can provide several configuration options to make: * PREFIX=path — Installation prefix (default=/usr/local) * MAN_PREFIX=path — Manpages installation prefix (default=$PREFIX/share/man/man8) * CONF_PREFIX=path — Configuration files installation prefix (default=$PREFIX/etc/minbif) * DOC_PREFIX=path — Documentation files installation prefix (default=$PREFIX/share/doc/minbif) * ENABLE_MINBIF=(ON|OFF) — Compile minbif (default=ON). * ENABLE_IMLIB=(ON|OFF) — Use imlib2 (default=ON). * ENABLE_CACA=(ON|OFF) — Do not use libcaca features to display icons (default=ON). * ENABLE_VIDEO=(ON|OFF) — Enable the video viewer (default=OFF). Depends on ENABLE_CACA. * ENABLE_PLUGIN=(ON|OFF) — Compile libpurple's plugins (default=OFF). * ENABLE_PAM=(ON|OFF) — Compile with PAM support (default=OFF). * ENABLE_TLS=(ON|OFF) — Compile with TLS support (default=OFF). * DEBUG=(ON|OFF) — Compile with debug (default=OFF). Run: $ make [PREFIX=path] [MAN_PREFIX=path] [CONF_PREFIX=path] [DOC_PREFIX=path] [ENABLE_MINBIF=ON|OFF] [ENABLE_CACA=ON|OFF] [ENABLE_VIDEO=ON|OFF] [ENABLE_PLUGIN=ON|OFF] [ENABLE_PAM=ON|OFF] [ENABLE_TLS=ON|OFF] [DEBUG=ON|OFF] To install it use: $ make install 3. Configuration ================ Copy the minbif.conf file and edit it. 3.1 Inetd --------- Run: # update-inetd --add '6667 stream tcp nowait username /usr/sbin/tcpd /usr/bin/minbif /etc/minbif/minbif.conf' Then, restart inetd, and use an IRC client to connect to minbif. 3.2 Daemon Fork --------------- Set the irc/type parameter in configuration to 2 (as daemon fork) and write a irc/daemon block to set the address and port to bind. Now, run the minbif daemon: # minbif /path/to/minbif.conf A new forked process will be created every time a new connection is established. Note: there isn't (yet?) any fork limit. Your system is vulnerable to a fork bomb, so set the RLIMIT_NPROC ulimit parameter to prevent that way. 4. Documentation ================ You can see an online documentation on the Minbif website at http://minbif.im 5. Support ========== A problem? A suggestion? - Ask your question on the #minbif channel on the FreeNode IRC network. - Subscribe to the mailing list at http://lists.symlink.me/mailman/listinfo/minbif - Send an email to a developer (see the AUTHORS file). minbif-1.0.5+git20120508/cmake-modules/0000755000175000017500000000000011754152001015210 5ustar sebsebminbif-1.0.5+git20120508/cmake-modules/FindPAM.cmake0000644000175000017500000000522611754152001017435 0ustar sebseb# - Try to find the PAM libraries # Once done this will define # # PAM_FOUND - system has pam # PAM_INCLUDE_DIR - the pam include directory # PAM_LIBRARIES - libpam library # # This code is borrowed from kdebase-workspace # # Copyright: © 2008 Kevin Kofler # © 2006 Alexander Neundorf # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 dated June, 1991. # # This package is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # if (PAM_INCLUDE_DIR AND PAM_LIBRARY) # Already in cache, be silent set(PAM_FIND_QUIETLY TRUE) endif (PAM_INCLUDE_DIR AND PAM_LIBRARY) find_path(PAM_INCLUDE_DIR NAMES security/pam_appl.h pam/pam_appl.h) find_library(PAM_LIBRARY pam) find_library(DL_LIBRARY dl) if (PAM_INCLUDE_DIR AND PAM_LIBRARY) set(PAM_FOUND TRUE) if (DL_LIBRARY) set(PAM_LIBRARIES ${PAM_LIBRARY} ${DL_LIBRARY}) else (DL_LIBRARY) set(PAM_LIBRARIES ${PAM_LIBRARY}) endif (DL_LIBRARY) if (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h) # darwin claims to be something special set(HAVE_PAM_PAM_APPL_H 1) endif (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h) if (NOT DEFINED PAM_MESSAGE_CONST) include(CheckCXXSourceCompiles) # XXX does this work with plain c? check_cxx_source_compiles(" #if ${HAVE_PAM_PAM_APPL_H}+0 # include #else # include #endif static int PAM_conv( int num_msg, const struct pam_message **msg, /* this is the culprit */ struct pam_response **resp, void *ctx) { return 0; } int main(void) { struct pam_conv PAM_conversation = { &PAM_conv, /* this bombs out if the above does not match */ 0 }; return 0; } " PAM_MESSAGE_CONST) endif (NOT DEFINED PAM_MESSAGE_CONST) set(PAM_MESSAGE_CONST ${PAM_MESSAGE_CONST} CACHE BOOL "PAM expects a conversation function with const pam_message") endif (PAM_INCLUDE_DIR AND PAM_LIBRARY) if (PAM_FOUND) if (NOT PAM_FIND_QUIETLY) message(STATUS "Found PAM: ${PAM_LIBRARIES}") endif (NOT PAM_FIND_QUIETLY) else (PAM_FOUND) if (PAM_FIND_REQUIRED) message(FATAL_ERROR "PAM was not found") endif(PAM_FIND_REQUIRED) endif (PAM_FOUND) mark_as_advanced(PAM_INCLUDE_DIR PAM_LIBRARY DL_LIBRARY PAM_MESSAGE_CONST)