A Perl IMAP proxy that does some folder filtering, picked up at Mac OS X Hints.
#! /usr/bin/perl # Copyright (C) 2004 Lars Eggert. # All rights reserved. # # Redistribution and use in source and binary forms are permitted # provided that the above copyright notice and this paragraph are # duplicated in all such forms and that any documentation, # advertising materials, and other materials related to such # distribution and use acknowledge that the software was developed # by the author. The name of the author may not be used to endorse # or promote products derived from this software without specific # prior written permission. # # THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE. # $Id$ use warnings; use strict; use IO::Select; # uncomment if SSL suport is required # use IO::Socket::SSL; use POSIX; # these need to be perl regexps matching the desired IMAP folders, for example: my @subscribed = qw(INBOX.* user.ietf user.ietf.(announce|hip|ietf|ipsec|postel.*) user.ietf.(rfc-interest|tcpm|tsvwg|xml2rfc) user.mmi.FreeBSD.*); # Daemonize! sub daemon () { sub _fork { if (defined (my $pid = fork)) { return $pid; } else { die "cannot fork: $!"; } } # fork and exit parent if (_fork) { exit 0; } # detach ourselves from the terminal POSIX::setsid or die "cannot detach from controlling terminal"; # prevent possibility of acquiring a controlling terminal $SIG{'HUP'} = 'IGNORE'; if (_fork) { exit 0; } # change working directory chdir "/"; # clear file creation mask umask 0; # close open file descriptors foreach my $i (0..POSIX::sysconf(&POSIX::_SC_OPEN_MAX)) { POSIX::close $i; } # Reopen stderr, stdout, stdin to /dev/null open STDIN, "+>/dev/null"; open STDOUT, "| /usr/bin/logger -p user.notice"; open STDERR, "+>&STDOUT"; STDOUT->autoflush(1); STDERR->autoflush(1); } my $client = "localhost:143"; # 143 = imap my (%mua, %imap, %done, %data); daemon; my $inbound = new IO::Socket::INET(LocalHost => $client, Listen => 15, ReuseAddr => 1); my $select = IO::Select->new($inbound); while (1) { my @ready = $select->can_read(5); foreach my $fd (@ready) { # a MUA is opening a new connection to us, open relay to server if ($fd == $inbound) { # be smart about what NEC server to use my $new_mua = $inbound->accept; my $new_imap = new IO::Socket::INET("IMAP-SERVER:143"); # IMAPS is also possible # my $new_imap = new IO::Socket::SSL("IMAPS-SERVER:993"); unless (defined $new_imap) { $new_mua->close; } else { $select->add($new_mua, $new_imap); $mua{$new_mua} = $new_imap; $imap{$new_imap} = $new_mua; } # the IMAP server is sending something to the MUA, filter and relay } elsif (exists $imap{$fd}) { while(1) { $fd->blocking(0); my $result = sysread $fd, $_, 16384; if (not defined $result) { # if we've filtered once before, no need to do this at all last if $done{$fd} or not defined $data{$fd}; # check if we have a full mboxlist in data, and if so, # return a filtered version to the MUA if ($data{$fd} =~ /^* LIST () /m) { # data contains list of imap folders if ($data{$fd} =~ /^d+ OK Completed (.*)/m) { # data contains COMPLETE list of folders, filter it # (only folders matching regexps in @subscribed # will be returned to the MUA) foreach my $mbox (@subscribed) { $data{$fd} =~ s/^(*s+)LIST(s+() s+.*"$mbox"rn) /$1KEEP$2/gmx; } $data{$fd} =~ s/^* LIST () .*rn//gm; $data{$fd} =~ s/^(* )KEEP( () .*rn)/$1LIST$2/gm; $imap{$fd}->blocking(1); syswrite $imap{$fd}, $data{$fd}; delete $data{$fd}; $done{$fd} = 1; } } else { # data contains something else, just relay it $imap{$fd}->blocking(1); syswrite $imap{$fd}, $data{$fd}; delete $data{$fd}; } last; } elsif ($result > 0) { if ($done{$fd}) { $imap{$fd}->blocking(1); syswrite $imap{$fd}, $_; } else { $data{$fd} .= $_; } } elsif ($result == 0) { # EOF, close the connections $select->remove($fd, $imap{$fd}); $fd->close; $imap{$fd}->close; delete $data{$fd}; delete $done{$fd}; last; } } # the MUA is sending something to the IMAP server, just relay } elsif (exists $mua{$fd}) { while (1) { $fd->blocking(0); my $result = sysread $fd, $_, 16384; if (not defined $result) { last; } elsif ($result > 0) { $mua{$fd}->blocking(1); syswrite $mua{$fd}, $_; } elsif ($result == 0) { # EOF, close the connections $select->remove($fd, $mua{$fd}); $fd->close; $mua{$fd}->close; last; } } } } } $inbound->close;