--- /dev/null
+#!/usr/bin/perl
+
+use vars qw($VERSION %IRSSI);
+
+use Irssi 20020704;
+$VERSION = "1.2";
+%IRSSI = (
+ authors => "Jochen 'c0ffee' Eisinger",
+ contact => "c0ffee\@penguin-breeder.org",
+ name => "SILC2 MIME handler",
+ description => "This script implements MIME handlers for SILC2, according to draft-riikonen-silc-flags-payloads-00",
+ license => "GPL2 or any later",
+ url => "http://www.penguin-breeder.org/silc/",
+ changed => "Wed Aug 29 10:45 CET 2003",
+);
+
+use Irssi::Silc;
+
+use MIME::Parser;
+use Mail::Field;
+use Mail::Cap;
+use File::MMagic;
+use IO::Scalar;
+use IO::File;
+use File::Temp qw/ tempfile /;
+use Sys::Hostname;
+use POSIX qw/ ceil /;
+
+my @mcaps;
+my $magic = new File::MMagic;
+
+##
+# read_mime_database
+#
+# Loads all mailcap databases specified in the setting
+# mime_database. Default is ~/.mailcap and /etc/mailcap in
+# that order. Function is invoked on startup.
+#
+# MIME Magic Info is also read...
+sub read_mime_database {
+ # read mailcap databases rfc1525
+ foreach (split /\s+/, Irssi::settings_get_str("mime_database")) {
+ if (( -f $_ ) and ( -R $_ )) {
+ Irssi::printformat(MSGLEVEL_CRAP, 'load_mailcap', $_)
+ if Irssi::settings_get_bool("mime_verbose");
+ $mcap = new Mail::Cap $_;
+ push @mcaps, $mcap;
+ } else {
+ Irssi::printformat(MSGLEVEL_CRAP, 'load_mailcap_fail', $_)
+ if Irssi::settings_get_bool("mime_verbose");
+ }
+ }
+
+ $mfile = Irssi::settings_get_str("mime_magic");
+
+ if ($mfile ne "") {
+ Irssi::printformat(MSGLEVEL_CRAP, 'load_mime_magic', $mfile);
+ $magic = File::MMagic::new($mfile);
+ }
+
+ if ( not -d Irssi::settings_get_str("mime_temp_dir")) {
+
+ Irssi::printformat(MSGLEVEL_CRAP, 'no_temp_dir',
+ Irssi::settings_get_str("mime_temp_dir"));
+
+ Irssi::settings_set_str("mime_temp_dir", "/tmp");
+
+ }
+}
+
+##
+# privmsg_get_query
+#
+# get or create query
+#
+sub privmsg_get_query {
+ my ($server, $target, $own) = @_;
+
+ $dest = $server->query_find($target);
+ if (not defined $dest &&
+ (Irssi::level2bits(settings_get_str("autocreate_query_level")) &
+ MSGLEVEL_MSGS) &&
+ (!$own || Irssi::settings_get_bool("autocreate_own_query"))) {
+ $dest = Irssi::Silc::Server::query_create($server->{tag}, $target, 1);
+ }
+
+ return $dest;
+}
+
+##
+# unescape
+#
+# Removes the null-byte escaping from a data block. Returns the
+# unescaped data. All data send via mime signals must be escaped.
+sub unescape {
+ my ($escaped) = @_;
+ $escaped =~ s/\001\001/\000/g;
+ $escaped =~ s/\001\002/\001/g;
+
+ return $escaped;
+}
+
+##
+# escape
+#
+# Escapes null-bytes for signal transfer. Used to transfer binary data
+# in null-terminated strings. Returns the escaped data. All data send
+# via mime signals must be escaped.
+sub escape {
+ my ($unescaped) = @_;
+ $unescaped =~ s/\001/\001\002/g;
+ $unescaped =~ s/\000/\001\001/g;
+
+ return $unescaped;
+}
+
+##
+# background_exec
+#
+# fork and execute
+#
+sub background_exec {
+ my ($server, $witem, $signed, $sender, $type, $cmd) = @_;
+
+ if ($signed == -1) {
+ $format = "mime_data_received";
+ } elsif ($signed == 0) {
+ $format = "mime_data_received_signed";
+ } elsif ($signed == 1) {
+ $format = "mime_data_received_unknown";
+ } elsif ($signed == 2) {
+ $format = "mime_data_received_failed";
+ }
+
+ if (not $witem->{type}) {
+ $witem = privmsg_get_query($server, $sender, 0);
+ }
+
+ if ($witem->{type}) {
+ $witem->printformat(MSGLEVEL_CRAP, $format, $sender, $type);
+ $witem->window()->command("EXEC " . Irssi::settings_get_str("mime_exec_param") . " " .
+ $cmd);
+ } else {
+ Irssi::printformat(MSGLEVEL_CRAP, $format, $sender, $type);
+ Irssi::command("EXEC " . Irssi::settings_get_str("mime_exec_param") . " " .
+ $cmd);
+ }
+
+}
+
+my %partial;
+
+##
+# process_mime_entity(WI_ITEM_REC, $signed, $sender, MIME::Entity $msg)
+#
+# -1 failure, 0 success
+sub process_mime_entity {
+ my ($server, $witem, $signed, $sender, $entity) = @_;
+
+ my ($mimetype, $fh, $tempfile, $parser, $ret, $io, $mcap, $cmd);
+
+ $mimetype = Mail::Field->new('Content-type', $entity->head->get('Content-Type'));
+
+ # check whether this is message/partial
+ if ($mimetype->type eq "message/partial") {
+
+ # without an ID i don't know what stream this is related to
+ if ($mimetype->id eq "") {
+ Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "no ID");
+ return -1;
+ }
+
+ # the first packet is treated seperatly
+ if ($mimetype->number == 1) {
+
+ # the IDs should be unique
+ if (defined $partial{$mimetype->id}) {
+ Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "duplicate ID");
+ $fh = $partial{$mimetype->id}{file};
+ $fh->close;
+ unlink $partial{$mimetype->id}{name};
+ undef $partial{$mimetype->id};
+ return -1;
+ }
+
+ # create a new record
+ $partial{$mimetype->id}{received} = 1;
+ ($fh, $partial{$mimetype->id}{name})= tempfile("msg-XXXXXXXX", SUFFIX => ".dat", DIR => Irssi::settings_get_str("mime_temp_dir"));
+ $partial{$mimetype->id}{file} = $fh;
+ $partial{$mimetype->id}{count} = 1;
+ $partial{$mimetype->id}{total} = $mimetype->total;
+
+ } else { # 2nd and later packets
+
+ # detect unknown IDs
+ if (not defined $partial{$mimetype->id}) {
+ Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "unknown ID");
+ return -1;
+ }
+
+ # the 'total' information can be set in any packet,
+ # however it has to be the same all the time
+ if ($mimetype->total > 0) {
+
+ if (($partial{$mimetype->id}{total} > 0) &&
+ ($partial{$mimetype->id}{total} != $mimetype->total)) {
+ Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "invalid count");
+ $fh = $partial{$mimetype->id}{file};
+ $fh->close;
+ unlink $partial{$mimetype->id}{name};
+ undef $partial{$mimetype->id};
+ return -1;
+ }
+
+ $partial{$mimetype->id}{total} = $mimetype->total;
+
+ }
+
+ # we expect to receive packets in order
+ if ($mimetype->number != ($partial{$mimetype->id}{count} + 1)) {
+ Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "invalid sequence number");
+ $fh = $partial{$mimetype->id}{file};
+ $fh->close;
+ unlink $partial{$mimetype->id}{name};
+ undef $partial{$mimetype->id};
+ return -1;
+ }
+
+ # update our sequence record and save the packet
+ $partial{$mimetype->id}{count} = $mimetype->number;
+
+ }
+
+ # and save the packet
+ $fh = $partial{$mimetype->id}{file};
+ if ($io = $entity->bodyhandle->open("r")) {
+ while (defined($_ = $io->getline)) { print $fh $_ }
+ $io->close;
+ }
+
+ # return if this wasn't the last packet
+ if (($partial{$mimetype->id}{total} == 0) ||
+ ($partial{$mimetype->id}{count} < $partial{$mimetype->id}{total})) {
+ return 1;
+ }
+
+ # last packet...
+ $tempfile = $partial{$mimetype->id}{name};
+ $fh = $partial{$mimetype->id}{file};
+ $fh->close;
+ undef $partial{$mimetype->id};
+
+ $parser = new MIME::Parser;
+ $parser->output_dir(Irssi::settings_get_str("mime_temp_dir"));
+ $mime = $parser->parse_open($tempfile);
+
+ $ret = process_mime_entity($server, $witem, $signed, $sender, $mime);
+
+ $parser->filer->purge;
+ unlink $tempfile;
+ return $ret;
+
+ }
+
+ # we could check for */parityfec (RTP packets) rfc2733, 3009
+
+ # save to temporary file
+ ($fh, $tempfile) = tempfile("msg-XXXXXXXX", SUFFIX => ".dat", DIR => Irssi::settings_get_str("mime_temp_dir"));
+ if ($io = $entity->open("r")) {
+ while (defined($_ = $io->getline)) { print $fh $_; }
+ $io->close;
+ }
+ close $fh;
+
+ # try to handle it
+ foreach $mcap (@mcaps) {
+
+ $cmd = $mcap->viewCmd($mimetype->type, $tempfile);
+ next if not defined $cmd;
+
+ background_exec($server, $witem, $signed, $sender, $mimetype->type, $cmd);
+ return 1;
+ }
+
+ unlink $tempfile if Irssi::settings_get_bool("mime_unlink_tempfiles");
+ return $mimetype->type;
+}
+
+##
+# sig_mime
+#
+# signal handler for incoming MIME type messages. If the encoding or
+# the content type are missing or not parsable, they default to binary
+# and application/octet-stream respectivly. If a decoder for the given
+# transfer encoding is available, the message is decoded. If a handler
+# for the given content type is available in one of the mailcap databases,
+# the handler is invoked and the signal is stopped. The mailcap databases
+# are scanned in order of loading. Temporary files are unlinked if the
+# setting mime_unlink_tempfiles is true.
+sub sig_mime {
+
+ my ($server, $witem, $blob, $sender, $verified) = @_;
+
+ $parser = new MIME::Parser;
+ $parser->output_dir(Irssi::settings_get_str("mime_temp_dir"));
+ $mime = $parser->parse_data(unescape($blob));
+
+ $ret = process_mime_entity($server, $witem, $verified, $sender, $mime);
+
+ $parser->filer->purge;
+
+ if ($ret == 1) {
+ Irssi::signal_stop();
+ } elsif ($ret == -1) {
+ return;
+ } else {
+ if (not $witem->{type}) {
+ $witem = privmsg_get_query($server, $sender, 0);
+ }
+ $theme = $witem->{theme} || Irssi::current_theme;
+ $format = $theme->get_format("fe-common/silc", "message_data");
+ $format =~ s/\$0/$sender/;
+ $format =~ s/\$1/$ret/;
+ if ($witem->{type}) {
+ $witem->print($theme->format_expand($format));
+ } else {
+ Irssi::print($theme->format_expand($format));
+ }
+ Irssi::signal_stop();
+ }
+}
+
+##
+# cmd_mmsg
+#
+# Sends a file with a given MIME type and transfer encoding.
+#
+# MMSG [<-sign>] [<-channel>] <target> <file> [<type> [<encoding>]]
+#
+# Sends a private data message to other user in the network. The message
+# will be send as a MIME encoded data message.
+#
+# If -channel option is provided then this command actually send channel
+# message to the specified channel. The message IS NOT private message, it
+# is normal channel message.
+#
+# If the -sign optin is provided, the message will be additionally
+# signed.
+#
+# Messages that exceed roughly 64k have to be split up into smaller packets.
+# This is done automatically.
+#
+# If no transfer-encoding is given it defaults to binary or 7bit for messages
+# that have to be split up.
+#
+# If no content-type is given it is guessed using a MIME magic file.
+#
+# Settings
+#
+# mime_magic - path to MIME magic file, or internal
+# defaults if empty
+# mime_default_encoding - encoding to use if none specified
+# mime_temp_dir - where to store temporary files
+#
+# Examples
+#
+# /MMSG Foobar smiley.gif image/gif binary
+# /MMSG -channel silc silc.patch text/x-patch 7bit
+# /MMSG * boing.mp3 audio/mpeg
+sub cmd_mmsg {
+ my ($data, $server, $witem) = @_;
+
+ if ($server->{chat_type} ne "SILC") {
+ Irssi::printformat(MSGLEVEL_CRAP, 'mmsg_chattype');
+ return;
+ }
+
+ ($sign, $is_channel, $target, $file, $type, $encoding) =
+ $data =~ /^\s*(?:(-sign)?\s+)? # match the -sign
+ \s*(?:(-channel)?\s+)? # match the -channel
+ (\*|\S+)\s+ # target
+ (\S+) # filename
+ (?:\s+(\S+) # mime type
+ (?:\s+(\S+))?)?\s* # encoding
+ $/ix;
+
+ Irssi::printformat(MSGLEVEL_CRAP, 'mmsg_parameters'), return
+ if ($target eq "") or ($file eq "");
+
+ Irssi::printformat(MSGLEVEL_CRAP, 'mmsg_file', $file), return
+ if not ( -f $file );
+
+ $type = $magic->checktype_filename($file)
+ if not defined $type;
+ $encoding = Irssi::settings_get_str("mime_default_encoding")
+ if not defined $encoding;
+
+ # does the target exist? we don't test that... especially the
+ # -channel parameter is ignored :/
+
+ if ($target eq "*") {
+
+ $is_channel = ($witem->{type} eq "CHANNEL" ? "-channel" : "");
+ $target = $witem->{name};
+
+ }
+
+ $entity = new MIME::Entity->build(
+ 'MIME-Version' => "1.0",
+ Encoding => $encoding,
+ Type => $type,
+ Path => $file
+ );
+
+ ($fh, $tempfile) = tempfile( DIR => Irssi::settings_get_str("mime_temp_dir"));
+ $entity->print($fh);
+ close $fh;
+
+ $is_channel = (lc($is_channel) eq "-channel" ? 1 : 0);
+ $sign = (lc($sign) eq "-sign" ? 1 : 0);
+
+ if ($is_channel) {
+ $dest = $server->channel_find($target);
+ } else {
+ $dest = privmsg_get_query($server, $target, 1);
+ }
+
+
+ # 21:27 <@pekka> c0ffee: the core routines will crop the message if it
+ # doesn't fit.. I would use a bit shorter than the MAX_LEN
+ # 21:28 <@pekka> c0ffee: -1024 bytes is sufficient
+ # 21:28 <@pekka> c0ffee: should be sufficient in all possible cases
+ if ((stat($tempfile))[7] < 0xfbff) {
+ $format = ($sign ? "mime_data_send_signed" : "mime_data_send");
+ if ($dest->{type}) {
+ $dest->printformat(MSGLEVEL_CRAP, $format, $type);
+ } else {
+ Irssi::printformat(MSGLEVEL_CRAP, $format, $type);
+ }
+
+ unlink $tempfile;
+ Irssi::signal_emit("mime-send", $server, \$is_channel,
+ $target, escape($entity->stringify), \$sign);
+ } else {
+
+ $format = ($sign ? "mime_data_multi_signed" : "mime_data_multi");
+ $chunks = ceil((stat $tempfile)[7] / 0xfb00);
+ if ($dest->{type}) {
+ $dest->printformat(MSGLEVEL_CRAP, $format, $type, $chunks);
+ } else {
+ Irssi::printformat(MSGLEVEL_CRAP, $format, $type, $chunks);
+ }
+
+ open TFILE, $tempfile;
+ $id = sprintf "id-%06d-%08d\@%s", int(rand(65535)), time(), hostname();;
+ $chunks = 0;
+ do {
+ read TFILE, $data, 0xfb00;
+ $chunks++;
+
+ $entity = new MIME::Entity->build(
+ 'MIME-Version' => "1.0",
+ Encoding => "binary",
+ Type => "message/partial; id=\"$id\"; number=$chunks" .
+ (eof(TFILE) ? "; total=$chunks" : ""),
+ Data => $data
+ );
+ Irssi::signal_emit("mime-send", $server, \$is_channel,
+ $target, escape($entity->stringify), \$sign);
+
+ } while (!eof(TFILE));
+ close TFILE;
+
+ unlink $tempfile;
+ }
+}
+
+# Signal handlers
+Irssi::signal_add("mime", "sig_mime");
+
+# Commands
+Irssi::command_bind("mmsg", "cmd_mmsg");
+
+# Settings
+Irssi::settings_add_str("misc", "mime_database",
+ "$ENV{HOME}/.mailcap /etc/mailcap");
+Irssi::settings_add_bool("misc", "mime_unlink_tempfiles", 1);
+Irssi::settings_add_str("misc", "mime_default_encoding", "binary");
+Irssi::settings_add_bool("misc", "mime_verbose", 0);
+Irssi::settings_add_str("misc", "mime_temp_dir", "/tmp");
+Irssi::settings_add_str("misc", "mime_magic", "");
+Irssi::settings_add_str("misc", "mime_exec_param", "-");
+
+# Init
+Irssi::theme_register(['load_mailcap', 'Loading mailcaps from {hilight $0}',
+ 'load_mailcap_fail', 'Couldn\'t find {hilight $0}',
+ 'message_partial_failure', 'message/partial: {hilight $0-}',
+ 'mmsg_chattype', 'command was not designed for this chat type',
+ 'mmsg_parameters', 'not enough parameters given',
+ 'mmsg_file', 'File {hilight $0} not found',
+ 'load_mime_magic', 'Loading MIME magic types from {hilight $0}',
+ 'no_temp_dir', 'Directory {hilight $0} does not exist, defaulting to /tmp',
+ 'mime_data_received', '{nick $0} sent "{hilight $1}" data message',
+ 'mime_data_received_signed', '{nick $0} sent "{hilight $1}" data message (signature {flag_signed})',
+ 'mime_data_received_unknown', '{nick $0} sent "{hilight $1}" data message (signature {flag_unknown})',
+ 'mime_data_received_failed', '{nick $0} sent "{hilight $1}" data message (signature {flag_failed})',
+ 'mime_data_send', 'sending "{hilight $0}" data message',
+ 'mime_data_send_signed', 'sending "{hilight $0}" data message (signature {flag_signed})',
+ 'mime_data_multi', 'sending "{hilight $0}" data message ($1 chunks)',
+ 'mime_data_multi_signed', 'sending "{hilight $0}" data message ($1 chunks, signature {flag_signed})']);
+
+
+
+read_mime_database();