Всем доброй хабранедели!
Итак, цель статьи: рассказ о том, как автоматически разбирать входящую почту с примерами обработки.
Попутно: как я считерил и выиграл аж 8 из 10 комплектов планшетов у DX и сдался китайцам.
Итак, цель номер ноль: научиться разбирать инкам методами, отличными от «зайти по POP/IMAP раз в минуту».
Я решил, что helloWorld это не так интересно, тем более на глаза попалась лотерейка от DX (dx.com/newsletters).
Вкратце работает она так:
0. Вводим email на страничке подписки (HTTP POST)
1. На него приходит ссылка для подтверждения email
2. Проходим по ссылке
3. Если email зарегистрирован впервые, получаем на email lucky number, который и участвует в розыгрыше
Внимательный читатель уже понял суть довольно простого «финта ушами», но цель статьи в практических мелочах.
Итак ресурсы: дешевая тестовая vds-ка.
Ненужный домен ocrd.ru (A и MX записи сейчас указывают в никуда)
Около часа времени вечером.
Шаг1: генерация.
Поскольку нам нужны уникальные email, пишем генератор. (У нас будет что-то типа AvksentijIvanov@ocrd.ru )
(список 100 популярных славянских имён и фамилий взят из википедии)
function ngen(){ /*Генерируем ту часть, которая перед @*/
$names=file(dirname(__FILE__).'/names.txt');
$surnames=file(dirname(__FILE__).'/surnames.txt');
return trl($names[rand(0,count($names)-1)]).'-'.trl($surnames[rand(0,count($surnames)-1)]);
// тут я иногда менял местами части и или убирал "-" совсем или заменял, например, на точку или _.
}
function trl($text) {/*Функция транслтерации, пропустим из-за простоты*/}
function _l($v){/*Ф-ия пишет $v в лог (с датой и кодом действия), пропустим*/}
// скрипт запускается каждую минуту. Подписываем по N email-ов в минуту, N=2;
for ($i=0; $i<2;$i++){
$email=ngen();
$email=preg_replace('/[^A-Za-z-.]/','',$email); // на всякий случай, вдруг мы транслитерация учитывает не все буквы.
$ch = curl_init('http://dx.com/newsletters');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // хотя зачем, всё равно не разбираем
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.0; rv:16.0) Gecko/20100101 Firefox/16.0');
curl_setopt($ch, CURLOPT_POSTFIELDS, 'email='.$email.'%40ocrd.ru&CheckedItems=5555&CheckedItems=4444&CheckedItems=8888&CheckedItems=9999&CheckedItems=3001&CheckedItems=3002&CheckedItems=3003&CheckedItems=3004&CheckedItems=199&CheckedItems=499&CheckedItems=799&CheckedItems=899&CheckedItems=599&CheckedItems=399&CheckedItems=699&CheckedItems=999&CheckedItems=1099&CheckedItems=1299&CheckedItems=1499&CheckedItems=1599&CheckedItems=1699&CheckedItems=1799'); // копипаст из firebug
$r= curl_exec($ch);
_l("-in {$email}");
sleep(10);
}
Шаг2: Настраиваем экзим.
Ставим его апт-гетом (для удобства понимания я указал split configuration into files), не буду это подробно расписывать.
Добавляем транспорт:
# /etc/exim4/conf.d/transport/01-ocrd_pipe
ocrd_pipe:
driver = pipe
return_fail_output
command = /usr/bin/php5 -f /var/mail/ocrd_mail.php $local_part
Как вы уже догадались, содержимое письма придёт в STDIN указанному скрипту. Локальную часть (та что до @) я отправляю как аргумент, просто чтобы не заниматься её выгребанием из сообщения, хотя это и не сложно.
Добавляем роутер
# /etc/exim4/conf.d/router/01-ocrd-pipe
# этот роутер я добавил позднее , чтобы получать почту некоторых юзернеймов(которых выбрали, кхм, победителями) на специально созданный ящик на яндексе
fwla:
driver = redirect
no_verify
local_parts = tst:test:shilovyaroslav:valeryanyakushev:rogovanisim:potapovignatij:lukakarpov:avksentijuvarov:voronovfilat:kondratgavrilov
data = ocrdmail@ya.ru
unseen # дадим отработать остальным роутерам.
# базовый роутер -- пайп в скрипт
ocrd:
driver = accept
domains = ocrd.ru
transport = ocrd_pipe
Перегенерируем конфиг(update-exim4.conf и рестарт)
Шаг3: разбор входящего сообщения.
Само сообщение парсит утилита munpack (apt-get [или что там у вас] её)
Внимание, адовый говнокод детектед. Если вы слишком впечатлительны просто не разворачивайте спойлер.
<?php
if (!isset($argv[1])) die();
$msg = '';
while(!feof(STDIN)) {
$msg .= fread(STDIN, 1024);
}
// Эстеты могут читать хоть через file_get_contents из php://input =)
$dir='/var/mail/ocrd.ru/';
$to=$argv[1];
// сохраним сообщение
$fname=$to.'.'.date('dmy.His.').rand(1000,9999);
$mf=$dir.'/'.$fname.'.msg';
file_put_contents($mf,$msg,FILE_APPEND);
// потрём временный файл, который остаётся от munpack
// из него мы будем читать распарсеное сообщение.
$f=$dir.'/tempdesc.txt';
unlink($f);
// Есть куча разных способов парсить mime messages, но такой самый простой по затратам на реализацию.
$v=exec('munpack -f -C '.$dir.' '.$mf);
_l("msg for {$to} {$fname}");
$c=file_get_contents($f);
// пробуем найти в письме ссылку активации подписки.
$v=activate_nl($to,$c) ;
// fail? ищем лаки намбер
if (!$v) $v1= get_number($to,$c);
// epic fail is so epic?
if (!$v and !$v1) _l("err: not_detected ".$fname);
// разложим почту по папкам ddmmyy/<action>
$t='err';
if ($v) $t='act';
if ($v1)$t='nbr';
$dc=$dir.'/'.date('dmy').'/'.$t;
if ( !is_dir($dc)) mkdir($dc,0750,true);
rename($mf,$dc.'/'.$fname.'.msg');
function activate_nl($to,$c){ // смотрим, пришла ли нам ссылка активации
global $dir,$fname; // это айайай. вы со мной согласны?
echo "Act_NLn";
$regex='#/newsletters/confirmSubscribe?e=(.+)%40ocrd.ru&c=(d+)"#';
$m=array();
if(! preg_match($regex,$c,$m)) return false;
// пришла -- заходим по ней (самый самый простой способ, ага)
_l("cnf for {$to}: e{$m[1]} c{$m[2]}");
file_get_contents("http://dx.com/newsletters/confirmSubscribe?e={$m[1]}%40ocrd.ru&c={$m[2]}");
return true;
}
function get_number($to,$c){ // нам пришел ID для розыгрыша, пишем его в лог.
global $fname,$dir;
echo "Act_GNn";
$regex='#Yours+luckys+numbers+isD+(d+)s*<#';
$m=array();
if(! preg_match($regex,$c,$m)) return false;
_l("lnm for {$to}: {$m[1]}");
return true;
}
function _l($v){ /*Просто пишем в лог*/}
В общем, это было оставлено работать примерно на полмесяца с N=3;
За ноябрь было получено около 37000 счастливых номеров из примерно 45 тысяч участвующих в розыгрыше (счастливые номера автоинкременты, поэтому всё хорошо видно).
Я исходил из того, что разыгрывается один комлект призов, поэтому и поставил N=3 генерациям в минуту.
Итоги
Дальнейшее развите событий: «Думаешь, что всех уел? Нет, браток, ты ох… как был не прав.»
В первых числах декабря были объявлены победители, что вызвало резонный батхерт у комьюнити DX.
Из 10 выбранных победителей (да прибудет с нами Гаусс), 8 оказались «у меня» (что и не мудрено, тридцать семь сорок пятых это 82%).
Я включил форвард ящиков победителей (см. роутер fwla) и начал наблюдать за ситуацией. Всем 8 виртуалам пришли уведомления о том, что «поздравляем, вы выиграли.»
Тут я сдался китайцам. Мол читер я, раздайте еще кому нибудь, но вот мой адрес (думал хоть один, но вышлют, наивный я). Ан нет, через несколько дней китайцы написали буквально следущее.
Since we hadn't expected that some client would cheat in the drawing by making fake email addresses, we didn't check all the winners’ email addresses. Now we remove all the fake email addresses and make a new round of drawing to ensure the fairness.
Ну в общем, такие они, китаёзы…
Вопросы — в каменты, опечатки — в личку, хабракат — в пост, лучи ненависти — китайцам.
Спасибо, что дочитали до конца.
Автор: la0