1 Introduction
Previously, we covered techniques for sending messages in PowerShell. This leaves us the problem of being able to receive email in PowerShell. Ultimately, the goal here is to perform POP3 (Post Office Protocol 3) communications [18] without installing an email application, and only use lightweight libraries to implement the following features:
- Support SSL authentication;
- Save multiple attachments to files;
- Save/load entire email messages to/from files;
- Delete email messages on the remote server.
This article will address some of these requirements in detail. Of course, source code will be made available in the Appendix section.
2 Receiving Email in PowerShell
PowerShell offers direct support for email dispatching through the Send-MailMessage
command [13]. Alternatively, we can also tap into the .NET Frameworks and send messages with the System.Net.Mail.SmtpClient
class [7]. Unfortunately, PowerShell does not feature complementary commands for fetching messages through the POP3 protocol. Nor does it offer IMAP support. Therefore, we will be looking at a third-party solution for fetching emails, such as the OpenPOP.NET library [1, 2].
The official OpenPOP.NET website provides a comprehensive documentation for the library [11], and also a number of useful examples [12]. Translating some of those examples to PowerShell should be relatively easy. In the next few sections we will explore the following OpenPOP.NET classes:
-
OpenPop.Pop3.Pop3Client
- Allows applications to fetch email by using the Post Office Protocol 3 (POP3); -
OpenPop.Mime.Header.MessageHeader
- Class that holds all headers for a message; -
OpenPop.Mime.Message
- Represents an email message that is fetched by thePop3Client
class.
We will also briefly look at the System.Net.Mail
classes listed below [14].
-
System.Net.Mail.MailMessage
- Represents an email message that can be sent using theSmtpClient
class; -
System.Net.Mail.Attachment
- Represents an attachment to an email.
2.1 Connecting to the POP3 Server
In order to communicate with the POP3 server, we need to construct a Pop3Client
class, then initialise it with the following information: the POP3 server and port, credentials for authentication, and indicate whether to use SSL cryptographic protocols for communication. We can encapsulate all of those requirements into a convenient utility function, called makePOP3Client
:
function makePOP3Client
{
Param
(
[string] $server,
[int] $port,
[bool] $enableSSL,
[string] $username,
[string] $password
)
$pop3Client = New-Object OpenPop.Pop3.Pop3Client
$pop3Client.connect( $server, $port, $enableSSL )
if ( !$pop3Client.connected )
{
throw "Unable to create POP3 client. Connection failed with server $server"
}
$pop3Client.authenticate( $username, $password )
return $pop3Client
}
Most POP3 servers expect the connection to be secured with cryptographic protocols, such as the Secure Sockets Layer (SSL). Typically, POP3 servers open a dedicated port for encrypted traffic, as shown in the table below. However, always check what ports are available with your email service provider.
Cryptographic Protocol |
$port
|
$enableSSL
|
---|---|---|
None | 110 |
$false
|
Secure Sockets Layer (SSL) | 995 |
$true
|
The makePOP3Client
function will throw an exception if the connection fails. Your code should handle such exceptions gracefully. Once a connection is established with the POP3 server, the Pop3Client
instance can be reused to fetch multiple messages in a single batch operation.
2.2 Fetching Messages
The simple example below demonstrates how to fetch messages from a POP3 server. The fetchAndListMessages
function initially queries Pop3Client.GetMessageCount
to see how many messages are available on the server. Only the first $count
messages are fetched from the server in reversed order, because most servers give the latest message the highest index. Note that the available messages are indexed in the range of [1, $messageCount
] inclusive. We are not doing much with the messages at this stage, only display some of their header data in the console.
function fetchAndListMessages
{
Param
(
[OpenPop.Pop3.Pop3Client] $pop3Client,
[int] $count
)
$messageCount = $pop3Client.getMessageCount()
Write-Host "Messages available:" $messageCount
$messagesEnd = [math]::max( $messageCount - $count, 0 )
Write-Host "Fetching messages" $messageCount "to" ($messagesEnd + 1) "..."
for ( $messageIndex = $messageCount; $messageIndex -gt $messagesEnd; $messageIndex-- )
{
$incomingMessage = $pop3Client.getMessage( $messageIndex )
Write-Host "Message $messageIndex`:"
Write-Host "`tFrom:" $incomingMessage.headers.from
Write-Host "`tSubject:" $incomingMessage.headers.subject
Write-Host "`tDate:" $incomingMessage.headers.dateSent
}
}
2.3 Saving Messages to a File
Saving messages is actually very easy. OpenPOP.NET provides a convenience function Message.Save
that allows us to serialise the entire Message
class contents into an IO stream. The saveMessage
utility function has all the necessary boilerplate code to stream a Message
class to file:
function saveMessage
{
Param
(
[OpenPop.Mime.Message] $incomingMessage,
[string] $outURL
)
New-Item -Path $outURL -ItemType "File" -Force | Out-Null
$outStream = New-Object IO.FileStream $outURL, "Create"
$incomingMessage.save( $outStream )
$outStream.close()
}
The example below demonstrates how the saveMessage
utility function is used to dump a bunch of emails to files. The fetchAndSaveMessages
is similar to our earlier example (fetchAndListMessages
), except this time we are saving the first $count
messages to a temporary location, as specified by the path $tempURL
. Unique file names are generated from the UID associated with each message. The fetched messages are then saved at their respective unique file locations.
function fetchAndSaveMessages
{
Param
(
[OpenPop.Pop3.Pop3Client] $pop3Client,
[int] $count,
[string] $tempURL
)
$messageCount = $pop3Client.getMessageCount()
Write-Host "Messages available:" $messageCount
$messagesEnd = [math]::max( $messageCount - $count, 0 )
Write-Host "Saving messages" $messageCount "to" ($messagesEnd + 1) "..."
for ( $messageIndex = $messageCount; $messageIndex -gt $messagesEnd; $messageIndex-- )
{
$uid = $pop3Client.getMessageUid( $messageIndex )
$emailURL = Join-Path -Path $tempURL -ChildPath ($uid + ".eml")
Write-Host "Saving message $messageIndex to:" $emailURL
$incomingMessage = $pop3Client.getMessage( $messageIndex )
saveMessage $incomingMessage $emailURL
}
}
2.4 Saving Attachments to a File
In this section we will demonstrate the following:
- How to convert a
OpenPop.Mime.Message
object into aSystem.Net.Mail.MailMessage
object; and - How to save a
System.Net.Mail.Attachment
object to file.
The latter task is performed by the saveAttachment
function, as demonstrated in the code snippet below. All we're doing here is create an output file stream, at the location specified by $outURL
, and then we stream the attachment content to it.
function saveAttachment
{
Param
(
[System.Net.Mail.Attachment] $attachment,
[string] $outURL
)
New-Item -Path $outURL -ItemType "File" -Force | Out-Null
$outStream = New-Object IO.FileStream $outURL, "Create"
$attachment.contentStream.copyTo( $outStream )
$outStream.close()
}
OpenPOP.NET implements yet another convenience function, called Message.ToMailMessage
, that allows us to convert a OpenPop.Mime.Message
object directly into a System.Net.Mail.MailMessage
object. Such conversions are useful when we want to forward messages using the SmtpClient
class via the SMTP protocol [7, 19].
Side note: When forwarding messages, make sure the To
and From
address fields are correct. See the article, Using SMTP in PowerShell, for some examples. Converting to System.Net.Mail.MailMessage
objects will have also have some limitations, as some metadata will not be copied over. See the OpenPOP.NET documentation for details [16].
The System.Net.Mail.MailMessage
class is also much nicer to work with (a subjective opinion), because it implements properties that allows us to modify the message, or enumerate attachments with minimal code, as demonstrated in the fetchAndSaveAttachments
function below.
The following example code simply dumps the attachments (if any) for the first $count
messages fetched from the server. The file name of each attachment is constructed from the combination of the email's UID and the actual attachment name embedded in the message. We do this to prevent file name collisions between attachments that have identical name metadata.
function fetchAndSaveAttachments
{
Param
(
[OpenPop.Pop3.Pop3Client] $pop3Client,
[int] $count,
[string] $tempURL
)
$messageCount = $pop3Client.getMessageCount()
Write-Host "Messages available:" $messageCount
$messagesEnd = [math]::max( $messageCount - $count, 0 )
Write-Host "Saving attachments for messages" $messageCount "to" ($messagesEnd + 1) "..."
for ( $messageIndex = $messageCount; $messageIndex -gt $messagesEnd; $messageIndex-- )
{
$uid = $pop3Client.getMessageUid( $messageIndex )
$incomingMessage = $pop3Client.getMessage( $messageIndex ).toMailMessage()
Write-Host "Processing message $messageIndex..."
foreach ( $attachment in $incomingMessage.attachments )
{
$attachmentURL = Join-Path -Path $tempURL -ChildPath $uid | Join-Path -ChildPath $attachment.name
Write-Host "`tSaving attachment to:" $attachmentURL
saveAttachment $attachment $attachmentURL
}
}
}
2.5 Loading a Message from a File
OpenPOP.NET will load and save email files in the RFC-822 file format [15]. Emails can be read from a file stream using the static method Message::Load
. The loadMessage
function in the snippet below opens a file stream at $inURL
, reads its content, then returns the OpenPop.Mime.Message
object.
function loadMessage
{
Param ( [string] $inURL )
$inStream = New-Object IO.FileStream $inURL, "Open"
$message = [OpenPop.Mime.Message]::load( $inStream )
$inStream.close()
return $message
}
To debug the contents of an email file, we can use the loadAndListMessage
function, as shown in the next example. Just like before, we are using Message.ToMailMessage
to convert a OpenPop.Mime.Message
object directly into a System.Net.Mail.MailMessage
object, which is then used for displaying the email contents in the console.
function loadAndListMessage
{
Param ( [string] $inURL )
Write-Host "`Loading message from:" $inURL
$message = ( loadMessage $inURL ).toMailMessage()
Write-Host "`tFrom:" $message.from
Write-Host "`tTo:" $message.to
if ( $message.cc )
{
Write-Host "`tCC:" $message.cc
}
if ( $message.bcc )
{
Write-Host "`tBCC:" $message.bcc
}
if ( $message.replyToList )
{
Write-Host "`tReply To:" $message.replyToList
}
if ( $message.subject )
{
Write-Host "`tSubject:" $message.subject
}
if ( $message.body )
{
Write-Host "`tBody:" $message.body
}
}
2.6 Deleting Messages
OpenPOP.NET can also delete messages from the POP3 server. You can use the function Pop3Client.DeleteMessage
to mark a particular message for deletion. Alternatively, one can also take the scorched earth approach, and use the Pop3Client.DeleteAllMessages
function to clear out the entire inbox. In either case, the messages will not be deleted until a QUIT
command is sent to the POP3 server. This is done when you call Pop3Client.Disconnect
manually, or when the Pop3Client
object is disposed.
Warning: Deleting emails is a dangerous operation. In most cases, messages will be removed irreversibly. Therefore, consider setting up a toy email account for development and testing purposes. Never play around with this feature on a production email account.
2.7 Complete Demo
The code below consolidates all the different examples discussed in the previous sections. We are going to use a hypothetical Gmail account (recipient.example@gmail.com
) as the source for the messages. Communications between our script and the server will be conducted over SSL encryption.
Note how we are using Reflection.Assembly
library to manually load the OpenPOP.NET binary file from the specified location. This allows PowerShell to bind and work with external .NET Framework classes and assemblies. Once the library is loaded, we can connect to the POP3 server and run all the example functions. We manually call the Pop3Client.Disconnect
function at the end, which dispatches a QUIT
command to the POP3 server. Hypothetically, messages pending for deletion would be applied at this point; although the Pop3Client
destructor can also do this for us automatically.
As a final note, deleting sensitive data with the Remove-Variable
command is also good practice. Typically you'd want to delete variables that was previously used for containing user credentials, or server connection state, and so forth. For the extra paranoid, consider using Clear-Variable
before Remove-Variable
.
# Path configurations
$openPopLibraryURL = $ExecutionContext.SessionState.Path
.GetUnresolvedProviderPathFromPSPath( "..\Binaries\OpenPop.dll" )
$tempBaseURL = $ExecutionContext.SessionState.Path
.GetUnresolvedProviderPathFromPSPath( "..\Temp\" )
$testMessageURL = $ExecutionContext.SessionState.Path
.GetUnresolvedProviderPathFromPSPath( "..\Examples\Test-Message.eml" )
# Incoming email configuration used for fetching messages
$incomingUsername = "recipient.example@gmail.com"
$incomingPassword = "**********"
$incomingServer = "pop.gmail.com"
$incomingPortPOP3 = 995 # Normally 110 (not secure), or 995 (SSL)
$incomingEnableSSL = $true
# Connect to the POP3 server and fetch messages
[Reflection.Assembly]::LoadFile( $openPopLibraryURL )
try {
Write-Host "Connecting to POP3 server: $incomingServer`:$incomingPortPOP3"
$pop3Client = makePOP3Client `
$incomingServer $incomingPortPOP3 $incomingEnableSSL `
$incomingUsername $incomingPassword
Remove-Variable -Name incomingPassword
fetchAndListMessages $pop3Client 10
fetchAndSaveMessages $pop3Client 10 $tempBaseURL
fetchAndSaveAttachments $pop3Client 10 $tempBaseURL
loadAndListMessage $testMessageURL
Write-Host "Disconnecting from POP3 server: $incomingServer`:$incomingPortPOP3"
if ( $pop3Client.connected )
{
$pop3Client.disconnect()
}
$pop3Client.dispose()
Remove-Variable -Name pop3Client
}
catch { Write-Error "Caught exception:`n`t$PSItem" }
2.8 Notes and Caveats
First and foremost, be aware that incoming email messages is a huge threat vector for your IT infrastructure. Therefore, most of the problems you will face will be security related. This article does not address security and design issues, such as:
-
Hard coding user names and passwords in a script is not particular safe practice for a production environment. If you are planning to store user credentials in plain text format inside a script, check with your IT department's security policy first. These scripts should run on a locked-down server the very least.
-
The POP3 protocol does not present information, such as unread messages on the server. You must collect that information manually by maintaining a list of "read" email UIDs locally, and then comparing that list with incoming messages. In a nutshell, message management will become complicated, requiring local state tracking for the entire inbox — hardly ideal for something like a simple script.
-
The order which messages are fetched from the POP3 server could be arbitrary. The only true way to sort messages is by examining the timestamps in the header.
-
Consider using POP3 only for simple fetch-and-delete jobs on a dedicated email account. For example, a message relay script pops off emails from an account, processes it, and then sends it off to another address.
-
Downloading attachments will have serious security implications. Are the attached documents safe? Is there embedded code in the documents? Are the documents actually in the correct format and not some kind of executable? Are the document handling libraries robust against malformed content? Even if the emails come from a trusted sender, their content could have been compromised. Malicious attachments is a favourite attack vector bad actors.
-
Chances are that some form of aggressive filtering is needed to weed out garbage attachments. Depending on your threat model, white listing only a subset of content types might be a good start. However, you should probably go deeper and analyse the white-listed content directly. For example, check whether the MIME types correlate with signatures in the file header; or even test whether the document itself is not intentionally malformed to exploit vulnerabilities. This is a rabbit hole.
-
Consider implementing a watchdog system that terminates the script (and its process tree) when something goes wrong. You want to limit script resource utilisation when opening attachments. Use the watchdog to mitigate CPU load spikes, memory leaks, or other forms of DoS attacks caused by malicious attachments. An example DoS scenario would involve an attacker sending a bad PNG file that causes some versions of
libpng
to enter into an infinite loop [9]. This is just one example, but similar scenarios could exist for other file formats. -
Consider running such scripts in a virtual environment, isolated from the rest of the system. Spills from malicious attachments can be contained within that environment.
-
Bugs and unforeseen issues with the script can easily delete emails (permanently) from the POP3 server. Take a defensive approach to programming [8]. You don't want to clear out your entire inbox by accident.
-
Google may block any attempts of authentication that does not use TLS or SSL. You may need to configure your Gmail account to allow less secure apps [10]. Also note that connecting through a VPN could also cause Gmail to superfluously alert the account owner about suspicious activity.
3 Conclusion
In this article, we have demonstrated that one can leverage on the OpenPOP.NET library to control a POP3 inbox with minimal boilerplate code and with only a handful of functions. The POP3 protocol is suitable for performing simple PowerShell tasks, such as fetch-forward-delete operations that does not involve complex inbox management. However, implementing anything beyond a simple forwarding system will require a local database scheme for tracking incoming emails.
For future work, we could investigate using a more modern protocol for fetching emails, such as the Internet Message Access Protocol (IMAP) [17]. IMAP offers features, such as automatic message state tracking, partial fetching of messages, support for multiple connections to the same mailbox, server-side searches, and so forth. These features could potentially simplify email management on the client side, and thus making IMAP a good alternative to POP3 for PowerShell applications. Unfortunately, .NET Frameworks do not implement classes for IMAP communications either; and once again one must rely on third-party libraries to attain IMAP support. Potential libraries worth exploring include AE.Net.Mail [3], S22.Imap [4], MailSystem.NET [5], and imapx [6].
Ultimately, no matter what solution you go for, either POP3 or IMAP, keep your email management code as simple as possible. Always ask, is PowerShell a suitable environment for doing complex email communications? Perhaps there is a better way, such as deploying third party software, built specifically for the job. And as usual, have fun experimenting, but be safe when handling messages from untrusted sources.
References
-
OpenPop.NET, SourceForge, Date Retrieved 14 March 2018
https://sourceforge.net/projects/hpop/files/ -
OpenPop.NET, NuGet, Date Retrieved 14 March 2018
https://www.nuget.org/packages/OpenPop.NET/ -
AE.Net.Mail - A C# POP/IMAP Client Library, GitHub, Date Retrieved 14 March 2018
https://github.com/andyedinborough/aenetmail -
S22.Imap, GitHub, Date Retrieved 14 March 2018
https://github.com/smiley22/S22.Imap -
MailSystem.NET, GitHub, Date Retrieved 14 March 2018
https://github.com/pmengal/MailSystem.NET -
imapx, GitHub, Date Retrieved 14 March 2018
https://github.com/azanov/imapx -
SmtpClient
Class, Microsoft Developer Network, 2018, Date Retrieved 14 March 2018
https://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient(v=vs.110).aspx -
Defensive Programming, Wikipedia, The Free Encyclopedia, 8 March 2018, Date Retrieved 14 March 2018
https://en.wikipedia.org/wiki/Defensive_programming -
libpng Denial-of-service Vulnerability, Vulnerability Note VU#684412, Software Engineering Institute, Carnegie Mellon University, 25 February 2014, Date Retrieved 14 March 2018
https://www.kb.cert.org/vuls/id/684412 -
Let less secure apps use your account, Google, Date Retrieved 21 March 2018
https://support.google.com/accounts/answer/6010255?hl=en -
OpenPop.NET POP3 Library Documentation, OpenPOP.NET, Date Retrieved 21 March 2018
http://hpop.sourceforge.net/documentation/index.html -
Examples Overview, OpenPOP.NET, Date Retrieved 21 March 2018
http://hpop.sourceforge.net/examples.php -
Send-MailMessage
, Microsoft Documentation, 2018, Date Retrieved 14 March 2018
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage -
System.Net.Mail
Namespace, Microsoft Developer Network, 2018, Date Retrieved 14 March 2018
https://msdn.microsoft.com/en-us/library/system.net.mail(v=vs.110).aspx -
David H. Crocker, Standard for ARPA Internet Text Messages, RFC-822, Dept. of Electrical Engineering, University of Delaware, Newark, DE 19711, 13 August 1982, Date Retrieved 4 April 2018
https://tools.ietf.org/html/rfc822/ -
Message.ToMailMessage
Method, OpenPop.NET POP3 Library Documentation, OpenPOP.NET, Date Retrieved 4 April 2018
http://hpop.sourceforge.net/documentation/index.html -
M. Crispin, Internet Message Access Protocol - Version 4rev1, RFC-3501, University of Washington, March 2003, Date Retrieved 4 April 2018
https://tools.ietf.org/html/rfc3501/ -
M. Rose, Post Office Protocol - Version 3, RFC-1081, TWG, November 1988, Date Retrieved 4 April 2018
https://tools.ietf.org/html/rfc1081/ -
Jonathan B. Postel, Simple Mail Transfer Protocol, RFC-821, Information Sciences Institute, University of Southern California, August 1982, Date Retrieved 4 April 2018
https://tools.ietf.org/html/rfc821/
Appendix
Source Code and Binaries
- Complete Source Code - GitHub