Почтовый робот и CRON

Автор статьи: Дмитрий Бородин ©
Сайт Автора: php.spb.ru
E-mail Автора: Нет
Дата публикации: 13.07.2005

Почтовый робот - это программа, которая анализирует почту, пришедшую на определнный почтовый ящик, и производит разные действия. Основная цель этой статьи о почтовых роботах. Но раз уж тут будет упомянут крон, то и о нем.

Сразу хочется предупредить, что здесь будет рассказано о множестве способов сделать одну вещь разными способами, поэтому вам придется немного думать... Это к тому, что к статьтье по отправке писем с аттачем постоянно имеют притензии разные люди, которые не умееют заполнить ни "почтовый хост провайдера", ни даже "обратный адрес". Пока вы будете читать эту статью, вы узнаете о большом количестве ньюансов и поймете, что спрашивать или объяснять в 2х словах "как сделать почтового робота?" невозможно.

CRON - средство выполнять регулярные действия на серверах типа Unix. В Windows крон называют шедулером. Суть одна: делаем правила срабатывания крона, например раз в 2 минуты или в 23:59 по четвергам, и что делать, например запустить php-программу (чтобы та делала нужную работу). В форуме часто возникает вопрос "как делать что либо регулярно?". Именно крон решает эту проблему.

Способы обработки почты:

  • Способ "CRON" - можно проверять раз в минуту свой почтовый ящик и обрабатывать все письма. Еще можно сэмулировать крон тем, кто имеет халявный хостинг где-нибудь на f2s.com... На платном хостинге админа вам крон предоставит.
  • Способ "COMMAND" - можно запускать обработчик писем как только оно приходит. Самый прогрессивный способ. Ваша программа получает письмо входным потоком - удобнее придумать трудно.

Больше ничего придумать нельзя. Разве что совсем не реагировать на почту.

-- I -- Почтовый робот за 10 минут ("COMMAND")

Для тех, кто хочет по быстрому все настроить, будет полезна данная глава. После нее начнется менее конкретная теория.

Приступим. Разумеется, ваш сервер должен быть типа Unix, а не Windows. Еще вы должны иметь PHP в виде CGI программы. Попробуйте запустить из телнета комагду echo 123 | php (либо с путями: echo 123 | /usr/bin/php). Если вы увидите в ответ что-то, то ПХП на сервере есть. Напишут "файл не найден" - значит еще нет. Попробуйте поискать в других каталогах или спросить админа. Если действительно нет - нужно скомпилировать ПХП как CGI программу. Это сделать очень просто:

  • перейдите в каталог исходников ПХП
  • запустите ./configure со всеми параметрами, что и при первой компиляции (с апачем), только не пишите параметра --with-apache
  • скомпилируйте: make
  • инсталляцию НЕ запускайте (make install)
  • скопируйте образовавшийся файл php в каталоге компиляции в каталог /usr/bin

    1) Ваш домашний каталог.Он не должен быть доступен из веба, т.е. не совпадать ни с одним веб-каталогом на сервере. Например, ваши веб-страницы живут в каталоге /www, а ваш домашний каталог - /home/dima. Вот там то мы и будем писать робота. Если вы положите такой скрипт в веб-каталог, получиться натуральный троян, типа телнет-доступа для всех желающих на сервер. В скрипте ничего страшного нет, просто чтение входного потока.

    2) Создайте файл робота. Он будет в нашем домашнем каталоге /home/dima/mail.php (расширение никакого значения не имеет) следующего содержания:

    #!/usr/bin/php
    <?  // *это* - вторая строка файла

       $log=fopen("/tmp/php-robot.txt","a+"); // ведем лог, чтобы удостоветиться о приходе писем
       fputs($log,date("--- d/m/Y H:i:s ---\\n")); // пишем в лог текущее время
       $in=fopen("php://stdin","r") or die(); // открыли файл с письмом
       while (!feof($in)) { // запустили цикл по чтению строк письма
          fputs($log,fgets($in,10000)); // прочитали очередную строку и записали в лог
       }
       fclose($in);
       fclose($log);
    ?>

3) Как это работает. Мы еще не закончили, но уже видно, как это будет работать. Во-первых, данный скрипт будет запущен только при приходу письма. Во-вторых, вам подадут письмо в готовом виде - надо только открыть входной поток (файл со спец. именем php://stdin) и прочитать от туда текст. Представьте, что это обычный файл. После того, как вы прочитали письмо, функции почтового робота заканчиваются. Вы можете поместить письмо в переменную или сразу построчно обработать, но главное - это уже совсем другая история, каким образом реагировать на текст-письма (там есть заголовок, тело, поля в заголовке...). В примере мы записываем текст письма в файл /tmp/php-robot.txt (доступ к каталогу /tmp имеют все пользователи на сервере). Число 10`000 очень завышено. Максимальная длина письма, толи 1 Кбайт, толи 2 Кбайта (в этом пределе). Писем с большими по длине строками не бывает, это вам может гарантировать ваш почтовый сервер.

3) Настроим права на скрипт. Файл (mail.php) должен иметь атрибуты rwxr-xr-x, владелец и группа не имеют значения. Если вы умете юзать ФАР, то подведите курсор к файлу mail.php на FTP панели, нажмите Ctrl+A и проставьте все 9 галочек, кроме 5й и 8й слева.

4) Глюк с переводом каретки. Если вы создаете файл (mail.php) в Windows/DOS и потом копируете по FTP на сервер, глюк будет. Если создать файл прямо из консоли сервера каким-нибудь редактором (vi, joe, mc), то глюка не будет. Глюк заключается в разных переводых каретки. Причем надо конвертировать перевод каретки не во всем файле, а только в первой строке: #!/usr/bin/php. Глюк не имеет отношения к ПХП, а к Юниксам в целом. К сожалению, разработчики линуксов и прочего вместо того, чтобы профикситить сей давний глюк, особо проявившийся с появлением веба, занимаются не понятно чем. Чтобы исправить глюк, сравните на пререводы каретки у любого файла с сервера (не из веб-каталога) и файла с Windows. Итак решение по шагам:

  • создать файл в Windows с текстом, приведенным выше не под именем mail.php, а под именем mail2.php
  • переписать в свой домашний каталог файл mail2.php
  • запустить телнет, перейти в домашний каталог и выполнить fromdos < mail2.php > mail.php (если у вас нет телнета - попросите админа о такой мелочи)
  • стереть mail2.php (и останется только mail.php в правильном виде)

5) Вам нужен админ сервера. Если вы живете на платном хостинге, админ может и не согласиться, но если ваш хостинг по месту работы - местный админ сделать обязан. Админов, которые что-то заявляют о дырявости ПХП или ненадежности таких технологий надо гнать с позором. В функции администратора сервера входит защита веб-сервера и веб-программ от взлома через сам сервер, а не методом ошибок в веб-программах. (Веб-программер, разумеется, должен писать программы без ошибок.) В том числе админ должен думать, что будет, если кто-то начнет целенаправлено спамить ваш хост письмами (есть элементарная защита - настройки в sendmail). Итак, к делу - надо в файл /etc/aliases поместить строку

testmail:     |/home/dima/mail.php

Допустим, ваш основной домен - php.spb.ru. Тогда адрес почтового робота будет [email protected]. Если домен не совпадает с основным, то можно воспользоваться виртульными доменами sendmail'а, обычно это файл с названием virtual (если админ не умеет - на мыло).

6) Не забудем пересоздать базу из файла aliases. На некоторых линухах установлен не sendmail, а что-то другое. Там не нужно пересоздавать базу. Но если в каталоге /etc вы найдете файл aliases.db, то вам это надо (запускать с правами рута):

makemap hash /etc/aliases.db < /etc/aliases ; newaliases

Возможно, досточно выполнить только одну из этих команд... Но так будет работь.

7) Альтернатива 5+6 пунктам (выполнить либо пункты 5+6, либо 7). Пункт 5 работает надежно - рекомендуется. А этот - может и не работать. Зато, если вы имете телнет-доступ на сервер, можно попробовать обойтись и без админа. Проверим, будет ли работать. Создайте файл /home/dima/.forward (не потеряйте точку). В файле строку:

|/home/dima/mail.php

Данный файл (.forward) дает возможноть любому пользователю, у когорого есть домашний каталог и шелл (только FTP - недостаточно), перенаправлять свою почту на нужные адреса (их может быть много) или запускать команды по приходу письма. E-mail пишут как есть, а команды начинают с вертикальной черты. Много записей разделяют либо запятой, либо переводом строки. Сделайте так и пошлите письмо работу. Обратите внимание, теперь у робота будет адрес пользователя данного каталога /home/dima. Т.е. никаких "testmail" в таком случае сделать нельзя. Для этого существуют алиасы, что и было описано в пунктах 5+6. Адрес будет совпадать с ваши логином на сервере, домен будет основной, т.е. e-mail получиться таким: [email protected]. Если после письма файл логов не измениться - значит этот способ на данном сервере не работет (в принципе он должен работать).

8) Готово! Теперь шлем письмо на адрес робота и смотрим что получилось.

  • файл логов создан и содержит письмо - все работает.
  • файл логов есть, но без письма ... сомнительная и маловероятная ситуация. Наверно запускали mail.php из консоли, а потом отправи письмо и оно не дошло, логи не изменились. Сотрите логи, повторно посшлите письмо.
  • файла логов нет, письмо назад не вернулось
    • проверьте, пришло ли оно как обычно письмо на сервер. Каталог с письмами обычно /var/spool/mail. Открыть файл testmail у вас прав не будет, но посмотреть на его размер и дату изменения - есть. Если файла вообще нет - смотрите в логи и т.п... (маловероятно)
    • письмо пришло как письмо, т.е. лежит в /var/spool/mail - забыли перегрузить алиасы
  • файла логов нет, письмо пришло назад - исправьте глюк с переводом каретки

9) О правах и пользователях. Вниманию админу. Файл mail.php в описанной выше конфигурации (5 пункт) будет запущен от пользователя/группы daemon:daemon (это не рут). Если это опасно, то с помощью команды "su" измените пользователя на нужного. Пример: su юзер -c команда. Либо воспользуйтесь sudo или спец битом у файла mail.php.

-- II -- Теория

Итак, мы пишем робота. Как мы определились выше, это будет программа на PHP, которая должна анализировать письма. Разумеется, не все сразу, а по очереди. Нам могут дать почту в двух видах: либо один файл с кучей писем (от 0 писем и более), либо файл с одним письмом (не более 1 письма). Второе разумеется удобнее, но не всегда доступно.

Давайте посмотрим из чего состоит одно письмо. Пример:

From [email protected]  Fri Jun 15 03:01:23 2001
Received: from host (host [195.220.4.134])
        by php.spb.ru with ESMTP id f5EN1NJ04208
        for <[email protected]>; Fri, 15 Jun 2001 03:01:23 +0400
Date: Fri, 15 Jun 2001 02:49:28 +0400
From: Dmitry Borodin <[email protected]>
X-Mailer: The Bat! (v1.51) Educational
X-Priority: 3 (Normal)
Message-ID: <[email protected]>
To: [email protected]
MIME-Version: 1.0
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit

Текст письма.
Текст письма.
Текст письма.

Давайте усвоим, что почтовый робот в данной статье - это программа, которая должна уметь получтать письма в виде файла (текст-примера выше). Как разбить данный текст на заголовок и тело, как вырезать из заголовока поля - это все относится к программированию, а не к веб-технологиям. Мы тут изучаем именно технологии.

В письме есть заголовок и тело. Заголовок идет с первой строки. Тело отделено от заголовка пустой строкой. Если вам дают файл с множеством писем, то письма разделяются довольно просто. Если строка начинается со слова "From " (5 символов, включая пробел) - то это начало нового заголовка. Да, хоть это и невероятно, но именно так письма можно разделить между собой. Возникает вопрос - а не может ли слово "From" встретиться в тексте письма? Нет. Может встетиться "From:" или еще что, но "From" с пробелом не встретиться. Возникает следующий вопрос - а нельзя ли в тексте письма вставить кучу слов "From" (с пробелом) и нарушить работу вражеского почтового сервера? Тоже нельзя :-) Умный сервер заменит "From" на ">From".

Подготовим ПХП для запуска робота

Как вы понимаете, мы пишем не веб-скрипт, который будет активироваться по желанию посетителя, который мышкой кликает по ссылкам, а по какому событию. В соответствии с этим, к примеру, не может идти речи о получении ИП-адреса посетителя или куках. Почтовый робот - это совершенно другая история. В его задачу входит найти файл с письмом(писами) и обработать.

Если вы ниже выберите вариант 1 или 2, вам Апач как CGI не нужен. И наоборот - если у вас нет Апача как CGI, то вы можете использовать только способы 1 или 2, чтобы организовать почтового робота или просто крон. В остальных случаях - Апач как CGI вам нужен. Если вы самостоятельно этого проверить не можете, обратитесь к администратору сервера с вопросом "Как запускать PHP в CGI-режиме?". Если скажет - нет такого, требуйте, чтобы появился :-) В конце концов вам от него нужно только адрес вида /usr/bin/php получить. Т.е. php - это название программы, а /usr/bin - каталог.

Вариант 1: без CRON, без Command, метод POP3 почты.
Это значит, что вы не имете доступа ни к крону, ни к описанному способу 'command'. Не расстраивайтесь :-) Наша почта находится либо на том же сервере, где и робот, либо на другом. Наша задача научиться забирать почту с POP3 ящика и выполнять данную функцию регулярно.

Как забрать почту с POP3 ящика. Надо изучить сокеты и POP3. Поверьте, это просто. Чтобы понять сокеты, предствьте, что это простые файлы, в которые можно писать и читать. Для забора почты нужно открыть сокет с сервером почты на 110 порту (порт POP3 сервера). Далее, как в файл, надо написать USER ваш_логин (пример: fputs($sock,"USER dima");), затем PASS ваш_пароль. После этого командой LIST получить список писем, еще командой RETR номер взять текст письма и стереть его с сервера. Всего надо изучить 6-7 команд протокала POP3. Это выходит за рамки данный статьи.

Теперь попробуем запускать нашу программу каждые 5 минут. К сожалению, методами ПХП напрямую это не возможно. Но мы попробуем. Допустим, на ваш сайт ходят люди :-) Если на ваш сайт никто не ходит, данный способ не заработает. Нужно сделать функцию (в каком-то общем файле), которую будут запускать из всех ваших скриптов. Функция должно проверять время модификации файла-флага, например, flag.txt. Если время модикации больше, чем 5 минут назад, то пора выполнять функицию проверки почты. После проверки почты, надо файл-флаг открыть на запись и закрыть, чтобы время модификации было изменено. Если же время модификации не превысело 5 минут, то функция ничего не делает. Как видите, ничего хитрого.

Ниже идет пример, который описывает более конкретно реализацию регулярного запуска фужного скрипта. Это не конкретная программа, а реальный пример.

<?php

function my_cron_script() {
/*
   Здесь нужно поместить команды, которые будут выполняться раз в 5 минут.
   Представьте, что эта функция - мини программа, поэтому пишите все только
   тут. Желательно сделать наиболее быстрой, т.к. браузер посетителя
   будет дожидаться (когда проходят очередные 5 минут) завершения
   работы всей программы. В общем, желательно, чтобы функция
   работала не болле 10 секунд.
*/
}


function check() {

   //  Эту функцию /вызов check()/ *нужно*:
   //   - вставить во все ваши файлы
   //   - желательно в конец
   //  Эту функцию *можно*:
   //   - ни разу не вызывать
   //   - вызывать два и более раз
   //  Если эта функция решит, что 5 минут прошло, она
   //  запустит 1 раз my_cron_script().

   // Надо создать flag.txt и дать на него права на запись для PHP.
   // Как это сделать, написано в http://php.spb.ru/php/chmod.html

   // Получаем время модификации (из атрибутов файла):
   $t=filemtime("flag.txt");

   // Цифра 5 в этом примере - 5 минут
   if ( $t + 5*60 < time() ) {
     
      // Есть вероятность, что параллельно с нами работает еще куча
      // таких же скриптов. Чтобы не допустить ошибки параллельного
      // выполнения (http://php.spb.ru/php/flock.html), мы заблокируем
      // файл и еще раз проверим дату обновления из него.
      $f=fopen("flag.txt","r+") or
         // Если вы не дадите прав для flag.txt, увидите такую ошибку на экране:
         die("</table></table></table><h1>ERROR in function check()</h1>");

      // Блокируем доступ к файлу  
      flock($f,2);

      // Читаем время обновления уже из файла
      $t=fgets($f,100);

      // "Генеральная" проверка времени
      if ( $t + 5*60 < time() ) {
        
         // Если условие выполнилось, то закончились очередные 5 минут
         // и нужно запустить скрипт my_cron_script(). Еще дополнительно
         // сбросим буфер с текстом, накопившемся за время выполнения
         // программы.
         flush();
         my_cron_script();

         // Пишем время модификации файла обратно в него. Атрибуты файла
         // (filemtime), которыми мы пользовались в начале этой фунции
         // обновляются сами собой (это делает ОС).

         // Переместить указатель записи на начало файла:
         ftell($f,0);
        
         // Записать время и пару пробелов, чтобы затереть старое число
         fputs($f,time()."    ");
      }
      // Закрываем файл, выходим из функции. Блокировка снимиться автоматически.
      fclose($f);
   }
}
?>

Вариант 2: без CRON, без Command, метод почты из файла.
Это значит, что вы по прежнему не имете доступа ни к крону, ни к способу 'command' и не понимаете сокетов с POP3. Тогда можно попытаться получать почту методом чтения ее из файла. Так читать можно только свою почту. Своя - это та, от чьего пользователя работает ПХП. Файл с почтой лежит в /var/spool/mail/имя_пользователя (или /var/mail). Настраиваем регулярный запуск проверки (описан в варианте 1) и для снятия почты открываем файл, читаем письма, делим их (там писем можнет быть от 0 и более) по слову "From" с пробелом. Попутно думаем, а кто еще будет иметь доступ к этому файлу почты.

Вариант 3: используем CRON
Крон - это средство вызвать ваш скрипт в нужное время. В вариантах 1 и 2 мы описали функцию для проверки почты. Здесь настроим ее регулярный запуск. Если у вас есть телнет на сервере, поищите каталог с кроном: /etc/cron, /var/spool/cron, /var/spool/cron/crontabs/, /usr/local/etc/cron и т.д... Попробуйте создать там файл с именем своего логина. К примеру для рута - файл "root". Такой файл там уже точно будет. Если получиться, то вы можете обойтись без админа. Создайте файл mail.php по образцу, описанному в первой главе. Там должна быть строка #!/usr/bin/php, но весь внутренний текст придется выкинуть. Там надо разметить функцию для проверки почты методом POP3 или из файла. Теперь вернемся к крону. Для автозапуска каждые 5 минут вашей программы mail.php нужно написать вот так: */5 * * * * /путь/к/файлу/mail.php Админу сервера, если вы самостоятельно не можете настроить крон, достаточно назвать время запуска и путь к файлу. После прописывния строки в файле крона его нужно перезапутить. Либо это сделает админ, либо вы (нажав Ctrl+Alt+Del на сервере), либо опять вы, но удаленно - завесив сервер кривой прогой на ПХП :-)

Вариант 4: используем COMMAND - запуск по приходу письма
Этот способ описан в первой главе. В файле mail.php выкидывайте содержимое и читайте пришедшее письмо, как простой файл. И делайте все, что нужно.

-- III -- Подведем итог

Мы узнали о разных способах получения почты:

  • из входного потока
  • из файла на сервере (в одном файле сразу несколько писем)
  • по POP3 протоколу

Мы узнали, как что-то делать регулярно:

  • реакция на письмо - метод COMMAND
  • регулярный запуск - с помощью специального средства CRON
  • почти регулярный запуск - методом проверки последного запуска

Эти способы можно комбинировать. Главное - не впадать в панику, понять теорию и сесть писать программу. О конкретных непонятных вещах всегда можно спросить.

Мы узнали, что самый продвинутый и легкий способ - это метод реакции на письмо (COMMAND), первая глава. К нему нужно стремиться. Писать примеры для других вариантов не имеет смысла. Они, конечно, тоже будут работать, но...

Мы узнали, что КРОН - это не страшный зверь, а файл всего с одной строкой вида */5 * * * * команда. Это средство есть на всех Юникс-серверах. Время можно настраивать очень гибко, но нам этого не надо (только лишь "раз в N минут").

Мы узнали, что нам скорее всего понадобиться ПХП как CGI-прогармма. Все привыкли использовать ПХП, вызываемый через веб (тут нам тип ПХП не важен - модуль Апача или CGI). А для выполнения скрипта требуется CGI. Может кто и не подозревал, но ПХП это отличное средство для написания разных скриптов даже у себя на рабочем компьюетере. В Windows можно легко регулярно копировать и упаковывать важные файлы, например (а то достал уже глючить). Вызывается проще простого: php.exe имя_файла. Еще, раз уж об Апаче разговор... Чтобы передать параметры в пхп-файл, вызыванный таким образом, надо сделать так: php.exe имя_файла &a=1&b=bbbb. Чтобы подавить строку X-Powered-By при таким запуске: ключ -q.


Список похожих статей