#!/usr/bin/perl -I../../../../.. -I. # TODO: Make logout/timeout facilities call a method on all the content modules so they can perform cleanup use strict; use Pos::Webtop::Web::ContentModule::Mail::Proxy; use Pos::Webtop::Web::ContentModule::Mail::Email; use Pos::Webtop::Web::ContentModule::Mail::Smtp; use MIME::Types; use MIME::Type; package Pos::Webtop::Web::ContentModule::Mail::ExchangeProxy; my $exchangeProxyHost = "198.247.175.182"; #XXX my $exchangeProxyPort = "5000"; our @ISA = qw( Pos::Webtop::Web::ContentModule::Mail::Proxy ); sub new { my $this = shift; my $class = ref($this) || $this; my ($self,$tb,$db,$exchange,$username,$password,$exchangeName) = @{{@_}}{qw/self tb db exchangeServer username password exchangeServerName/}; if ( not defined $self ) { $self = {}; bless($self,$class); } # setup base class my %args = @_; $args{'self'} = $self; $args{'tb'} = $tb; $args{'db'} = $db; Pos::Webtop::Web::ContentModule::Mail::Proxy->new( %args ); # initialize attributes $self->{'exchangeServer'} = $self->getSupernatAddress(host => $exchange); $self->{'exchangeServerName'} = $exchangeName; $self->{'username'} = $username; $self->{'password'} = $password; $self->{'loggedIn'} = 0; # Initialize userId my $client = $tb->getClient; $self->{'userId'} = $client->getUserId; $self->{'companyId'} = $client->getCustomerId; return $self; } sub getSupernatAddress { my $self = shift; my ($host) = @{{@_}}{qw/host/}; my $supernatHost = $self->{'tb'}->resolveHost(host => $host); my $new; if (defined($supernatHost) and defined($new = $self->{'tb'}->getSupernatInternalAddress(ip => $supernatHost, requestor => $exchangeProxyHost))) { $supernatHost = $new; } if (not defined($supernatHost)) { print STDERR "EXCHANGE: could not translate $host into supernat address.\n"; $supernatHost = $host; } return $supernatHost; } sub login { my $self = shift; if ($self->{'loggedIn'}) { return 1; } my $packet = $self->createPacket(method => 'LOGIN'); $self->addTlv(packet => $packet, type => 'PROFILE_USERNAME', value => $self->{'username'}); $self->addTlv(packet => $packet, type => 'PROFILE_PASSWORD', value => $self->{'password'}); $self->addTlv(packet => $packet, type => 'PROFILE_SERVER', value => $self->{'exchangeServer'}); $self->addTlv(packet => $packet, type => 'PROFILE_SERVER_NAME', value => $self->{'exchangeServerName'}); print STDERR "EXCHANGE: Logging in with user='" . $self->{'username'} . "' serverIp='" . $self->{'exchangeServer'} . "' serverName='" . $self->{'exchangeServerName'} . "'.\n"; my $response; if (not defined($response = $self->transmitPacket(packet => $packet))) { print STDERR "EXCHANGE: no login response received.\n"; return undef; } if (not defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { print STDERR "EXCHANGE: login was not successful.\n"; return undef; } $self->{'loggedIn'} = 1; return 1; } sub logout { my $self = shift; return undef; } sub checkEmail { my $self = shift; # This is a NOOP for exchange as we want to be constantly synchronized with the exchange server. # This sucks because it will cause slowness. return undef; } sub deleteEmail { my $self = shift; my ($emailId) = @{{@_}}{qw/emailId/}; my $packet = $self->createPacket(method => 'DELETE_EMAIL'); my $response; $self->addTlv(packet => $packet, type => 'EMAIL_EMAIL_ID', value => $emailId); if (not defined($response = $self->transmitPacket(packet => $packet)) or not defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { print STDERR "EXCHANGE: Did not get a successful deleteEmail response.\n"; return undef; } return 1; } sub sendEmail { my $self = shift; my ($to,$cc,$bcc,$subject,$body,$attachments) = @{{@_}}{qw/to cc bcc subject body attachments/}; if (!$self->login()) { return undef; } my $packet = $self->createPacket(method => 'SEND_EMAIL'); my $response; $self->addTlv(packet => $packet, type => 'EMAIL_TO', value => $to); if (defined($cc) and length($cc) > 0) { $self->addTlv(packet => $packet, type => 'EMAIL_CC', value => $cc); } if (defined($bcc) and length($bcc) > 0) { $self->addTlv(packet => $packet, type => 'EMAIL_BCC', value => $bcc); } $self->addTlv(packet => $packet, type => 'EMAIL_SUBJECT', value => $subject); $self->addTlv(packet => $packet, type => 'EMAIL_BODY', value => $body); foreach my $attachment (@{ $attachments }) { my %hash = %{ $attachment }; my $contentType = $hash{'ATTACHMENT_HEADERS'}->{uc('Content-Type')}; my $fileName = $hash{'ATTACHMENT_FILENAME'}; my $body = $hash{'ATTACHMENT_BODY'}; chomp($fileName); if (not defined($contentType)) { my $types = MIME::Types->new; my $type = $types->mimeTypeOf($fileName); my $mediaType = 'text'; my $subType = 'plain'; if (defined($type)) { $mediaType = $type->mediaType; $subType = $type->subType; } $contentType = "$mediaType/$subType"; } $self->addTlv(packet => $packet, type => 'EMAIL_ATTACH_MIME_TAG', value => $contentType); $self->addTlv(packet => $packet, type => 'EMAIL_ATTACH_FILE_NAME', value => $fileName); $self->addTlv(packet => $packet, type => 'EMAIL_ATTACH_BODY', value => $body); $self->addTlv(packet => $packet, type => 'EMAIL_ATTACH_BOUNDARY', value => 0); } if (not defined($response = $self->transmitPacket(packet => $packet)) or not defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { print STDERR "EXCHANGE: Did not get a successful sendEmail response.\n"; return undef; } return 1; } sub createFolder { my $self = shift; my ($folderName) = @{{@_}}{qw/folderName/}; if (not defined($self->login())) { return undef; } my $packet = $self->createPacket(method => 'CREATE_FOLDER'); my $response; $self->addTlv(packet => $packet, type => 'FOLDER_NAME', value => $folderName); if (not defined($response = $self->transmitPacket(packet => $packet)) or not defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { print STDERR "EXCHANGE: Did not get a successful createFolder response.\n"; return undef; } return 1; } sub getFolders { my $self = shift; if (not defined($self->login())) { return undef; } # If we haven't already queried our local copy... if ($self->{'cachedFolders'} == 0) { my $packet = $self->createPacket(method => 'GET_FOLDERS'); my $response; # If the packet transmits successfully and it responds successfully, update the cache. if (defined($response = $self->transmitPacket(packet => $packet)) and defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { my $index = 0; my $currentFolderName = undef; my $currentFolderNewCount = 0; my $tlv; while (defined($tlv = $self->enumTlv(packet => $response, index => $index))) { my ($type, $value) = @{ $tlv }; if ($type eq 'FOLDER_NAME') { $currentFolderName = $value; } elsif ($type eq 'FOLDER_NEW_COUNT') { $currentFolderNewCount = $value; } elsif ($type eq 'FOLDER_BOUNDARY') { #$currentFolderName =~ s/\\/\\\\/g; my ($folderId) = $self->{'db'}->query(sql => " select folder_id from w_user_email_folders where folder_name = '" . $self->{'db'}->escape(dirty => $currentFolderName) . "' AND user_id = '" . $self->{'userId'} . "' "); if (not defined($folderId)) { $self->{'db'}->do(sql => " insert into w_user_email_folders ( folder_id, user_id, folder_name, new_mail_count ) values( " . $self->{'db'}->getNextValueString( sequence => 'w_user_email_folders_pk') . ", '" . $self->{'userId'} . "', '" . $self->{'db'}->escape(dirty => $currentFolderName) . "', '" . $self->{'db'}->escape(dirty => $currentFolderNewCount) . "' )"); } else { $self->{'db'}->do(sql => " update w_user_email_folders set new_mail_count = '" . $self->{'db'}->escape(dirty => $currentFolderNewCount) . "' where folder_id = '$folderId' AND user_id = '" . $self->{'userId'} . "' "); } } $index++; } $self->{'db'}->commit; } $self->{'cachedFolders'} = 1; } # Now we load the folder list from the database my @folders = $self->{'db'}->query(sql => " select folder_name, new_mail_count from w_user_email_folders where user_id = '" . $self->{'userId'} . "' order by upper(folder_name) asc"); return (@folders); } sub moveToFolder { my $self = shift; my ($emailsSc, $folderName) = @{{@_}}{qw/emails folderName/}; if (not defined($self->login())) { return undef; } my $packet = $self->createPacket(method => 'MOVE_TO_FOLDER'); my $response; $self->addTlv(packet => $packet, type => 'FOLDER_NAME', value => $folderName); foreach my $emailId (@{ $emailsSc }) { $self->addTlv(packet => $packet, type => 'EMAIL_EMAIL_ID', value => $emailId); } if (not defined($response = $self->transmitPacket(packet => $packet)) or not defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { print STDERR "EXCHANGE: Did not get a successful moveToFolder response.\n"; return undef; } return 1; } sub getEmail { my $self = shift; my ($emailId) = @{{@_}}{qw/emailId/}; my %emailInformation; if (not defined($self->login())) { return undef; } my $packet = $self->createPacket(method => 'GET_EMAIL'); my $response; $self->addTlv(packet => $packet, type => 'EMAIL_EMAIL_ID', value => $emailId); my @attachments = (); $emailInformation{'EMAIL_ID'} = $emailId; $emailInformation{'EMAIL_ATTACHMENTS'} = \@attachments; if (not defined($response = $self->transmitPacket(packet => $packet)) or not defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { print STDERR "EXCHANGE: Did not get a successful getEmail response.\n"; return %emailInformation; } $emailInformation{'EMAIL_TO'} = $self->getTlv(packet => $response, type => 'EMAIL_TO'); $emailInformation{'EMAIL_CC'} = $self->getTlv(packet => $response, type => 'EMAIL_CC'); $emailInformation{'EMAIL_BCC'} = $self->getTlv(packet => $response, type => 'EMAIL_BCC'); $emailInformation{'EMAIL_FROM'} = $self->getTlv(packet => $response, type => 'EMAIL_FROM'); $emailInformation{'EMAIL_SUBJECT'} = $self->getTlv(packet => $response, type => 'EMAIL_SUBJECT'); $emailInformation{'EMAIL_DATE'} = $self->getTlv(packet => $response, type => 'EMAIL_DATE'); $emailInformation{'EMAIL_BODY'} = $self->getTlv(packet => $response, type => 'EMAIL_BODY'); $emailInformation{'EMAIL_NEXT_ID'} = $self->getTlv(packet => $response, type => 'EMAIL_NEXT_ID'); $emailInformation{'EMAIL_PREV_ID'} = $self->getTlv(packet => $response, type => 'EMAIL_PREV_ID'); # Now we enumerate the attachments my $count = 0; my %hash; my $tlv; # Add any attachments while (defined($tlv = $self->enumTlv(packet => $response, index => $count++))) { my ($type, $value) = @{ $tlv }; if ($type eq 'EMAIL_ATTACH_BOUNDARY') { my %realHash; $realHash{'ATTACHMENT_CONTENT_TYPE'} = $hash{'EMAIL_ATTACH_MIME_TAG'}; $realHash{'ATTACHMENT_DISPOSITION'} = "attachment; file=\"" . $hash{'EMAIL_ATTACH_FILE_NAME'} . "\""; $realHash{'ATTACHMENT_DESC'} = $hash{'EMAIL_ATTACH_FILE_NAME'}; $realHash{'ATTACHMENT_BODY'} = $hash{'EMAIL_ATTACH_BODY'}; push @{ $emailInformation{'EMAIL_ATTACHMENTS'} }, \%realHash; %hash = (); } else { $hash{$type} = $value; } } return %emailInformation; } sub getPreviewEmails { my $self = shift; my ($max, $folder) = @{{@_}}{qw/max folder/}; if (not defined($self->login)) { print STDERR "EXCHANGE: could not login\n"; return undef; } my $packet = $self->createPacket(method => 'GET_PREVIEW_EMAILS'); my $response; if (defined($folder)) { $self->addTlv(packet => $packet, type => 'EMAIL_FOLDER_NAME', value => $folder); } if (defined($max)) { $self->addTlv(packet => $packet, type => 'EMAIL_MAX', value => $max); } if (not defined($response = $self->transmitPacket(packet => $packet)) or not defined($self->getTlv(packet => $response, type => 'SUCCESS'))) { print STDERR "EXCHANGE: Did not get a successful preview response.\n"; return undef; } my $tlv; my @emails; my $index = 0; my %hash; while (defined($tlv = $self->enumTlv(packet => $response, index => $index))) { my ($type, $value) = @{ $tlv }; if ($type eq 'EMAIL_BOUNDARY') { my @email; push @email, $hash{'EMAIL_EMAIL_ID'}; push @email, $hash{'EMAIL_MESSAGE_ID'}; push @email, $hash{'EMAIL_TO'}; push @email, $hash{'EMAIL_FROM'}; push @email, $hash{'EMAIL_SUBJECT'}; push @email, $hash{'EMAIL_DATE'}; push @email, $hash{'EMAIL_IS_NEW'}; push @emails, \@email; %hash = (); } else { $hash{$type} = $value; } $index++; } return @emails; } sub getAdjacentEmails { my $self = shift; my ($emailId,$infoHashSc) = @{{@_}}{qw/emailId infoHash/}; my %infoHash; if (defined($infoHashSc)) { %infoHash = %{ $infoHashSc }; } my %hash; $hash{'PREV'} = $infoHash{'EMAIL_PREV_ID'}; $hash{'NEXT'} = $infoHash{'EMAIL_NEXT_ID'}; return \%hash; } sub cacheCredentials { my $self = shift; my ($username, $password) = @{{@_}}{qw/username password/}; $self->{'db'}->do(sql => " update w_user_email_settings_exchange set username = '" . $self->{'db'}->escape(dirty => $username) . "', password = '" . $self->{'db'}->escape(dirty => $password) . "' where user_id = '" . $self->{'userId'} . "' "); } sub markEmailAsRead { my $self = shift; my ($emailId) = @{{@_}}{qw/emailId/}; return undef; } sub doesFolderExist { my $self = shift; my ($folderName) = @{{@_}}{qw/folderName/}; if (not defined($self->login)) { return undef; } my @folders = $self->getFolders; foreach my $folder (@folders) { my ($name, $count) = @{ $folder }; if (not defined($name)) { next; } if ($name eq $folderName) { return 1; } } return undef; } sub getNextOutgoingMailId { my $self = shift; my $id = ""; foreach my $a (1..32) { $id .= int(rand(9)); } return $id; } # Internal my %methods; my %types; # Type encoding: # 0x00000000 for binary # 0x10000000 for null-term string # 0x20000000 for 4 byte integer $methods{'LOGIN'} = 0x0001; $methods{'LOGOUT'} = 0x0002; $methods{'CHECK_EMAIL'} = 0x0010; $methods{'GET_PREVIEW_EMAILS'} = 0x0011; $methods{'GET_EMAIL'} = 0x0012; $methods{'DELETE_EMAIL'} = 0x0013; $methods{'SEND_EMAIL'} = 0x0014; $methods{'GET_FOLDERS'} = 0x0020; $methods{'MOVE_TO_FOLDER'} = 0x0021; $methods{'CREATE_FOLDER'} = 0x0022; # # TLV's # $types{'SUCCESS'} = 0x00000000; # Login/Logout $types{'PROFILE_SERVER'} = 0x10000001; $types{'PROFILE_USERNAME'} = 0x10000002; $types{'PROFILE_PASSWORD'} = 0x10000003; $types{'PROFILE_SERVER_NAME'} = 0x10000004; # Emails $types{'EMAIL_EMAIL_ID'} = 0x10001000; $types{'EMAIL_MESSAGE_ID'} = 0x20001001; $types{'EMAIL_IS_NEW'} = 0x20001002; $types{'EMAIL_FROM'} = 0x10001003; $types{'EMAIL_TO'} = 0x10001004; $types{'EMAIL_CC'} = 0x10001005; $types{'EMAIL_BCC'} = 0x10001006; $types{'EMAIL_SUBJECT'} = 0x10001007; $types{'EMAIL_DATE'} = 0x10001008; $types{'EMAIL_BODY'} = 0x10001009; $types{'EMAIL_FOLDER_NAME'} = 0x1000100a; $types{'EMAIL_MAX'} = 0x2000100b; $types{'EMAIL_ATTACH_MIME_TAG'} = 0x10001100; $types{'EMAIL_ATTACH_FILE_NAME'} = 0x10001101; $types{'EMAIL_ATTACH_BODY'} = 0x00001102; $types{'EMAIL_ATTACH_BOUNDARY'} = 0x000011ff; $types{'EMAIL_PREV_ID'} = 0x1000100c; $types{'EMAIL_NEXT_ID'} = 0x1000100d; $types{'EMAIL_BOUNDARY'} = 0x00001fff; # Folders $types{'FOLDER_NAME'} = 0x10002000; $types{'FOLDER_NEW_COUNT'} = 0x20002001; $types{'FOLDER_BOUNDARY'} = 0x00002fff; sub createPacket { my $self = shift; my ($method) = @{{@_}}{qw/method/}; my $packet; $packet->{'length'} = 20; # length, method, user, company, session $packet->{'method'} = $methods{uc($method)}; $packet->{'tlvList'} = (); return $packet; } sub addTlv { my $self = shift; my ($packet, $type, $value) = @{{@_}}{qw/packet type value/}; if (not defined($packet) or not defined($type)) { return undef; } # if the high bit is set, it must be null terminated if ($types{uc($type)} & 0x10000000) { $value .= pack("c", 0x0); } elsif ($types{uc($type)} & 0x20000000) { $value = pack("N", $value); } my @tlv; push @tlv, $types{uc($type)}; push @tlv, $value; push @{ $packet->{'tlvList'} }, \@tlv; $packet->{'length'} += 8 + length($value); return 1; } sub translateTlvType { my $self = shift; my ($type) = @{{@_}}{qw/type/}; foreach my $key (keys %types) { if ($type == $types{uc($key)}) { return $key; } } return undef; } sub getTlv { my $self = shift; my ($packet, $type) = @{{@_}}{qw/packet type/}; my @trans = (); my $count = 0; my $tlv; while (defined($tlv = $self->enumTlv(packet => $packet, index => $count))) { my ($stringType, $value) = @{ $tlv }; if (uc($stringType) eq uc($type)) { return $value; } $count++; } return undef; } sub enumTlv { my $self = shift; my ($packet, $index) = @{{@_}}{qw/packet index/}; if (not defined($packet->{'tlvList'}) or $index >= scalar(@{ $packet->{'tlvList'} })) { return undef; } my @trans = (); my ($type, $value) = @{ @{ $packet->{'tlvList'} }[$index] }; my $stringType = $self->translateTlvType(type => $type); push @trans, $stringType; push @trans, $value; return \@trans; } sub toBuffer { my $self = shift; my ($packet) = @{{@_}}{qw/packet/}; my $rawBuffer = ""; # length | method | userId | companyId | sessionId $rawBuffer .= pack("N", $packet->{'length'}); $rawBuffer .= pack("N", $packet->{'method'}); $rawBuffer .= pack("N", $self->{'userId'}); $rawBuffer .= pack("N", $self->{'companyId'}); $rawBuffer .= pack("N", $self->{'tb'}->getClient->getSessionId); foreach my $tlv (@{ $packet->{'tlvList'} }) { my ($type, $value) = @{ $tlv }; my $length = 8 + length($value); $rawBuffer .= pack("N", $type); $rawBuffer .= pack("N", $length); $rawBuffer .= $value; } return $rawBuffer; } sub fromBuffer { my $self = shift; my ($buffer) = @{{@_}}{qw/buffer/}; my $packet; if (length($buffer) < 20) { return undef; } my $currentLength = 0; my $currentOffset = 0; $packet->{'length'} = int(unpack("N", substr($buffer, 0, 4))); $packet->{'method'} = int(unpack("N", substr($buffer, 4, 4))); $packet->{'userId'} = int(unpack("N", substr($buffer, 8, 4))); $packet->{'companyId'} = int(unpack("N", substr($buffer, 12, 4))); $packet->{'sessionId'} = int(unpack("N", substr($buffer, 16, 4))); $packet->{'tlvList'} = (); $currentLength = $packet->{'length'} - 20; $currentOffset = 20; while ($currentLength > 0) { my $type = int(unpack("N", substr($buffer, $currentOffset, 4))); my $length = int(unpack("N", substr($buffer, $currentOffset + 4, 4))); if ($length > $currentLength) { print STDERR "EXCHANGE: Invalid TLV len=$length actual=$currentLength\n"; last; } my $value = substr($buffer, $currentOffset + 8, ($length - 8)); my @tlv; # Remove the null term as perl does not care for it if ($type & 0x10000000) { my $safe = $/; $/ = pack ("c", 0x0); chomp($value); $/ = $safe; } elsif ($type & 0x20000000) { # If it's an integer, pack is from network byte order $value = unpack("N", $value); } push @tlv, $type; push @tlv, $value; push @{ $packet->{'tlvList'} }, \@tlv; $currentLength -= $length; $currentOffset += $length; } return $packet; } sub transmitPacket { my $self = shift; my ($packet) = @{{@_}}{qw/packet/}; my $response; my $buffer = $self->toBuffer(packet => $packet); if (not defined($self->{'exConnection'})) { $self->{'exConnection'} = IO::Socket::INET->new( PeerAddr => $exchangeProxyHost, PeerPort => $exchangeProxyPort, Proto => 'tcp'); } if (not defined($self->{'exConnection'})) { return undef; } # Transmit the packet syswrite($self->{'exConnection'}, $buffer, length($buffer)); # Now read the response $buffer = ""; sysread($self->{'exConnection'}, $buffer, 4); my $length = int(unpack("N", substr($buffer, 0, 4))); if (not defined($length) or $length <= 0) { return undef; } my $lengthLeft = $length - 4; while ($lengthLeft > 0) { my $append; my $lengthRead = sysread($self->{'exConnection'}, $append, $lengthLeft); $buffer .= $append; $lengthLeft -= $lengthRead; } return $self->fromBuffer(buffer => $buffer); } 1;