Чтение почты (POP3) в SQL Server 2005/2008

by Admin 17. декабря 2009 00:08

Продолжаю тему безполезных примеров CLR-сборок в помощь администратору(разработчику) БД.

В этот раз решил читать почту в SQL Server 2005/2008/2008R2 через протокол POP3. Многие скажут, что сервер БД не предназначен для этого, но ВДРУГ!?

Конечно для этого у нас есть SQL Mail, но учитывая ряд ограничений, о которых чуть ниже, пришлось посмотреть в сторону собственной сборки. Для чтения почты используются, как правило, протокол POP3 либо IMAP(о нём в следующий раз, возможно так же в качестве очередной CLR-сборки).

Ну и для начала всё-таки об ограничениях SQL Mail:

В будущей версии Microsoft SQL Server эта возможность будет удалена. Избегайте использования этой возможности в новых разработках и запланируйте изменение существующих приложений, в которых она применяется. Чтобы отправить почту из SQL Server, используйте компонент Database Mail.

...но как читать-то почту!? Ведь Database Mail только отправляет почту через SMTP-протокол...

Служба SQL Mail требует наличия подключения к почтовой станции, почтовый ящик, почтовый профиль и учетную запись пользователя домена Microsoft, используемую для входа на SQL Server (учетная запись пользователя должна быть в том же домене, что и SQL Server). Служба MSSQLServer должна быть запущена под этой учетной записью домена пользователя. С помощью расширенных хранимых процедур службы SQL Mail можно отправлять сообщения из триггера или хранимой процедуры. Хранимые процедуры службы SQL Mail могут обрабатывать запросы, полученные по электронной почте, и возвращать результирующий набор, создавая ответное электронное сообщение.

Эти ограничения заставили взглянуть в сторону изучения протоколов POP3/IMAP.

Разбирём POP3 чуть подробнее:

Краткое, но достаточно емкое описание почтового протокола POP3.

Перед работой через протокол POP3 сервер прослушивает порт 110. Когда клиент хочет использовать этот протокол, он должен создать TCP соединение с сервером. Когда соединение установлено, сервер отправляет приглашение. Затем клиент и POP3 сервер обмениваются информацией пока соединение не будет закрыто или прервано.

Команды POP3 состоят из ключевых слов, за некоторыми следует один или более аргументов. Все команды заканчиваются парой CRLF (в Visual Basic константа vbCrLf). Ключевые слова и аргументы состоят из печатаемых ASCII символов. Ключевое слово и аргументы разделены одиночным пробелом. Ключевое слово состоит от 3-х до 4-х символов, а аргумент может быть длиной до 40-ка символов.

Ответы в POP3 состоят из индикатора состояния и ключевого слова, за которым может следовать дополнительная информация. Ответ заканчивается парой CRLF. Существует только два индикатора состояния: "+OK" - положительный и "-ERR" - отрицательный.

Ответы на некоторые команды могут состоять из нескольких строк. В этих случаях каждая строка разделена парой CRLF, а конец ответа заканчивается ASCII символом 46 (".") и парой CRLF.

POP3 сессия состоит из нескольких режимов. Как только соединение с сервером было установлено и сервер отправил приглашение, то сессия переходит в режим авторизации. В этом режиме клиент должен идентифицировать себя на сервере. После успешной идентификации сессия переходит в режим транзакции. В этом режиме клиент запрашивает сервер выполнить определённые команды. Когда клиент отправляет команду QUIT, сессия переходит в режим обновления. В этом режиме POP3 сервер освобождает все занятые ресурсы и завершает работу. После этого TCP соединение закрывается.

Список команд:

  • USER - Когда РОРЗ -сессия находится в состоянии аутентификации (AUTHORIZATION), и клиент должен зарегистрировать себя на РОРЗ -сервере. Это может быть выполнено либо с помощью команд USER и PASS — ввод открытых пользовательского идентификатора и пароля (именно этот способ используется чаще), либо командой АРОР — аутентификация цифровой подписью, на базе секретного ключа. Любой РОРЗ -сервер должен поддерживать хотя бы один из механизмов аутентификации. Аргументом — "name" является строка, идентифицирующая почтовый ящик системы. Этот идентификатор должен быть уникальным в данной почтовой системе РОРЗ -сервера. Если ответом на эту команду является строка индикатора "+OK", клиент может отправлять команду PASS — ввод пароля или QUIT — завершить сессию. Если ответом является строка "-ERR", клиент может либо повторить команду USER, либо закрыть сессию
  • PASS - Аргументом команды является строка пароля данного почтового ящика. После получения команды PASS, РОРЗ -сервер, на основании аргументов команд USER и PASS, определяет возможность доступа к заданному почтовому ящику. Если РОРЗ -сервер ответил "+OK", это означает, что аутентификация клиента прошла успешно и он может работать со своим почтовым ящиком, т. е. сессия переходит в состояние TRANSACTION. Если РОРЗ- сервер ответил "-ERR", то либо был введен неверный пароль, либо не найден указанный почтовый ящик
  • STAT - После того как клиент успешно прошел процедуру аутентификации в РОРЗ- сервере, и РОРЗ- сервер "закрыл" определенный почтовый ящик только для использования данным клиентом (для тех, кто работал с базами данных, это называется EXCLUSIVE ACCESS LOCK), РОРЗ- сессия переходит в режим TRANSACTION, и клиент может начать работу со своей почтой Команда STAT (без аргументов) используется для просмотра состояния текущего почтового ящика. В ответ РОРЗ- сервер возвращает строку, содержащую количество и общий размер в байтах сообщений, которые клиент может получить с РОРЗ- сервера. Сообщения, помеченные на удаление, не учитываются
  • LIST [msg] - Команда LIST может передаваться как с аргументом msg — номером сообщения, так и без аргумента. Если команда содержит аргумент, и сообщение с указанным номером существует, ответом на нее будет "информационная строка", которая содержит номер сообщения и размер сообщения в байтах. Если аргумент не указан — ответом будет список информационных строк обо всех сообщениях в данном почтовом ящике. Сообщения, помеченные на удаление не фигурируют в этом списке
  • RETR msg - Используется для передачи клиенту запрашиваемого сообщения. Аргумент команды — номер сообщения. Если запрашиваемого сообщения нет, возвращается отрицательный индикатор "-ERR".
  • DELE msg - Аргумент команды— номер сообщения. Сообщения, помеченные на удаление, реально удаляются только после закрытия транзакции при отправке команды QUIT.
  • NOOP - Для проверки состояния соединения с РОРЗ- сервером используется команда NOOP. При активном соединении ответом на нее будет положительный индикатор "+ОК":
  • RSET - Для отката транзакции внутри сессии используется команда RSET (без аргументов). Если пользователь случайно пометил на удаление какие-либо сообщения, он может убрать эти пометки, отправив эту команду:
  • TOP msg n - По этой команде пользователь может получить "n" первых строк сообщения с номером "msg". РОРЗ- сервер по запросу клиента отправляет заголовок сообщения, затем пустую строку, затем требуемое количество строк сообщения (если количество строк в сообщении меньше указанного в параметре "n", пользователю передается все сообщение).
  • QUIT - К командам состояния AUTHORIZATION может относиться команда закрытия РОРЗ- сессии — QUIT, если она была отправлена в режиме AUTHORIZATION (например, при вводе неправильного пароля или идентификатора пользователя): Эта команда отправляется без аргументов и всегда имеет единственный ответ "+ОК".

Как видно из описания список команд не велик. В сборку я включил всего 3 команды: LIST(список писем), RETR(чтение письма), DELE(удаление письма). Все другие команды игнорируются, но вы можете сами дополнить мой пример, для этого и выкладываю исходный код сборки:

using System;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Net.Sockets;
using System.Text;

public class POP3CLR
{
    [SqlFunction(FillRowMethodName = "FillRow"
        , TableDefinition = "SERVER: nvarchar(max)")
    ]

    public static IEnumerable POP3Command(string POP3Server, string Port, string User, string Pass, string Command)
    {

        ArrayList rows = new ArrayList();
        if (Command.Length > 3)
        {
            if (Command != "LIST" && Command.Substring(0, 4) != "RETR" && Command.Substring(0, 4) != "DELE")
            {
                Command = "HELP";
            }
        }
        else
        {
            Command = "HELP";
        }

        if (Command == "HELP")
        {

            rows.Add(new object[] {"LIST-список информационных "+
                                   "строк обо всех сообщениях в данном почтовом ящике. "+
                                   "Сообщения, помеченные на удаление не фигурируют в этом списке." });
            rows.Add(new object[] {"RETR msg-Используется для передачи клиенту запрашиваемого сообщения. "+
                                   "Аргумент команды — номер сообщения. Если запрашиваемого сообщения нет, "+
                                   "возвращается отрицательный индикатор '-ERR'." });
            rows.Add(new object[] {"DELE msg-Аргумент команды— номер сообщения. "+
                                   "Сообщения, помеченные на удаление, реально удаляются только "+
                                   "после закрытия транзакции при отправке команды QUIT." });
            return rows;
        }

        TcpClient tcpClient = new TcpClient();
        tcpClient.Connect(POP3Server, Convert.ToInt32(Port));
        NetworkStream netStream = tcpClient.GetStream();
        System.IO.StreamReader strReader = new System.IO.StreamReader(netStream);

        if (tcpClient.Connected)
        {

            byte[] WriteBuffer = new byte[1024];
            ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            WriteBuffer = enc.GetBytes("USER " + User + "\r\n");
            netStream.Write(WriteBuffer, 0, WriteBuffer.Length);
            rows.Add(new object[] { strReader.ReadLine() + "\r\n\r\n" });
            WriteBuffer = enc.GetBytes("PASS " + Pass + "\r\n");
            netStream.Write(WriteBuffer, 0, WriteBuffer.Length);
            rows.Add(new object[] { strReader.ReadLine() + "\r\n\r\n" });
            WriteBuffer = enc.GetBytes(Command + "\r\n");
            netStream.Write(WriteBuffer, 0, WriteBuffer.Length);
            if (Command.Substring(0, 4) == "DELE")
            {
                rows.Add(new object[] { strReader.ReadLine() + "\r\n\r\n" });
            }
            else
            {
                string ListMessage;
                while (true)
                {
                    ListMessage = strReader.ReadLine();
                    if (ListMessage == ".")
                    {
                        break;
                    }
                    else
                    {
                        rows.Add(new object[] { ListMessage + "\r\n\r\n" });
                        continue;
                    }
                }
            }
            WriteBuffer = enc.GetBytes("QUIT\r\n");
            netStream.Write(WriteBuffer, 0, WriteBuffer.Length);
            rows.Add(new object[] { strReader.ReadLine() + "\r\n\r\n" });
        }
        return rows;
    }

    public static void FillRow(Object row, out string Server)
    {

        object[] xrow = (object[])row;
        Server = (string)xrow[0];

    }

}

Регистрируем сборку и создаём на её основе функцию:

CREATE ASSEMBLY AssemblyPOP3
FROM 'C:\CLR\POP3CLR.dll'
WITH PERMISSION_SET = UNSAFE
GO

--Создаём функцию
CREATE FUNCTION POP3Command
(
@POP3Server nvarchar(128),
@Port nvarchar(5),
@User nvarchar(128),
@Pass nvarchar(128),
@Command nvarchar(50)
)
RETURNS TABLE
(
[SERVER:] NVARCHAR(max)
)
EXTERNAL NAME AssemblyPOP3.POP3CLR.POP3Command;

Ну и пример работы с этой функцией:

SELECT * FROM POP3Command(
'pop3.MyServer.ru', 110, 'E-Mail@MyServer.ru', 'MyPassword', 'LIST'
)

Если у вас возникнут вопросы, то я буду рад помочь, пишите…

Tags: , , , ,

SQL Server

Комментарии (1) -

Сергей
Сергей Russia
17.12.2011 21:39:23 #

SELECT * FROM POP3Command('pop.mail.ru', 110, 'adres@bk.ru', 'password', 'LIST')

SERVER:
+OK    
+OK    
+OK Welcome!    
+OK 1 messages (3072 octets)    
1 2595    
+OK POP3 server at mail.ru signing off    

через 2-3 секунды повторяю запрос и все "висит". перезапуск студии помогает, но опять же на втором выполнении висит ..

Reply

Pingbacks and trackbacks (1)+

Добавить комментарий

  Country flag

biuquote
  • Комментарий
  • Предпросмотр
Loading