Как известно, практически во всех вагонах Московского Метро действуют Wi-Fi точки доступа, с помощью которых пользователи могут получить доступ в интернет и приятно скоротать время поездки в метро с работы домой: почитать новости, проверить почту, посмотреть котиков на YouTube и т.д.

Каждому устройству, прежде чем ему будет предоставлен доступ к сети интернет, необходимо аутентифицироваться. В первый раз пользователю на указанный номер телефона посылается СМС с кодом, после чего система запоминает MAC-адрес устройства и в дальнейшем пользователю для аутентификации требуется только нажать на ссылку «Войти в интернет» и немного подождать.

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

Если в метро вы не частый гость, то подобная схема может и не вызвать у вас раздражения, однако при ежедневном использовании она все таки надоедает, поэтому, как говорил один известный и харизматичный политик: «Хватит это терпеть!», сегодня мы будем автоматизировать аутентификацию в Московском метро.

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

Tasker — приложение, которое позволяет в зависимости от определенных условий (дата/время/местоположение/состояние устройств/показания датчиков и т.д.) производить определенные действия (отправку сообщений/вывод уведомлений/включение-отключение устройств/отрисовка простеньких интерфейсов и т.д.). Списки и условий, и действий просто огромны и зависят от версии Android и аппаратного оснащения устройства, так что приводить их полностью смысла нет.

Итак, после запуска Tasker, в первую очередь нужно перевести интерфейс на английский язык, ибо перевод тут хромает на обе ноги: Настройки->Интерфейс->Язык->English и перезапустить приложение. Теперь перед нами есть четыре вкладки:
  • Profiles — профили управляют связью между состоянием устройства/различными событиями и задачами;
  • Tasks — задачи описывают последовательность действий, которые необходимо выполнить;
  • Scenes — сцены это как бы самодельные формы, которые задачи могут создавать и настраивать, и контролы на которых могут запускать задачи;
  • Vars — список глобальных переменных, которые могут быть использованы для хранения данных между запусками задач.


Перейдем на закладку Tasks и создадим новую задачу, назвав ее Metro Auth:



В открывшемся окне нам необходимо для начала определить несколько переменных. Переменные определяются следующим образом:


  • Variable name — имя переменной, должно начинаться с символа % и состоять из строчных букв. Если в имени переменной будет хоть одна заглавная буква, то переменная станет глобальной, а нам это ни к чему;
  • To — значение переменной.

Так вот, нам необходимо создать следующие переменные:
  • %url — содержит ссылку на страницу, по которой мы будем тестировать нужна ли аутентификация или нет. Чем меньше объем передаваемых данных при этом — тем лучше (тело страницы все равно не нужно). С протоколом HTTPS возможны проблемы, так что лучше использовать HTTP;
  • %forms — содержит список идентификаторов HTML-форм, которые мы будем использовать для аутентификации. На момент написания статьи корректное значение — 'auth-form,hidden_form', однако если ребята из московского метро вдруг что-то изменят, я хотел бы показать как составить такой список самостоятельно, так что пока в качестве значения поставим один пробел (пустые переменные недопустимы);
  • %debug — эта переменная, при задании значения, отличного от нуля, будет вызывать показ дополнительной отладочной информации, которая поможет нам составить вышеупомянутый список форм.

Помимо простых действий, Tasker предоставляет нам возможность писать скрипты произвольной сложности с помощью нескольких инструментов. Мы будем использовать простой JavaScript:



Здесь необходимо выставить максимальный Timeout выполнения скрипта — 50 секунд, просто на всякий случай. Галочка Auto Exit отвечает за автоматическое завершения действия после завершения основного потока скрипта. В случае если используются асинхронные запросы (наш случай) или функция setTimeout, эту галочку необходимо снимать, а завершение действия определять самостоятельно с помощью функции exit();.

Сам скрипт я представлю в двух вариантах форматирования: приличное форматирование нужно если хочется рассмотреть скрипт, не сломав глаза, и форматирование под узкий экран позволяет скрипту выглядеть более-менее прилично на узком экране телефона. Изначально скрипт набирался на телефоне в «узком» варианте, и уже потом я переформатировал его для статьи:

Скрипт в приличном форматировании
function getUrl(url1,url2){
    url1=url1.split('?')[0];
    return url2.length?
        (/^http(s?):\/\//i.test(url2)?url2:
            (url2[0]=='/'?url1.split('/').slice(0,3).join('/')+url2:url1.split('/').slice(0,-1).join('/')+'/'+url2)
        ):url1;
}

function getVars(form,tag){
    vars='';
    fields=form.getElementsByTagName(tag);
    for(i=0;i<fields.length;i++)
        vars=vars+(i?'&':'')+fields[i].name+'='+fields[i].value;
    return vars;
}

function submit(xhr,request,form){
    request.url=getUrl(request.url,form.action);
    request.method=form.method;
    vars1=getVars(form,'input');
    vars2=getVars(form,'textarea');
    request.vars=vars1||vars2?(vars1?vars1:'')+(vars1&&vars2?'&':'')+(vars2?vars2:''):null;
    getPage(request,processPage,xhr);
}

function processPage(xhr,request){
    redir=xhr.getResponseHeader('Location');
    if(redir){
        if(redir==request.url) finalize('Ошибка: циклическое перенаправление');
        else{
            log('Перенаправление\n\n');
            getPage({'url':redir},processPage,xhr);
        }
    } else {
        forms=local('forms').split(',');
        id=null;
        for(i=0;i<forms.length;i++)
            if(xhr.response.getElementById(forms[i])) id=forms[i];
        if(id)submit(xhr,request,xhr.response.getElementById(id));
        else if(Number(local('debug'))){
            log('Формы на странице:\n');
            forms=xhr.response.getElementsByTagName('form');
            if(forms.length)
                for(i=0;i<forms.length;i++)
                    log((i?', "':'"')+forms[i].id+'"');
            else log('отсутствуют');
            finalize();
        } else finalize('Аутентификация успешна');
    }
}

function checkConn(xhr,request){
    redir=xhr.getResponseHeader('Location');
    if(redir){
        log('Перенаправление\n\n');
        getPage({'url':redir},processPage,xhr);
    } else {
        log('Аутентификация не требуется');
        finalize();
    }
}

function log(txt){
    logs=logs+(txt?txt:'');
}

function requestToText(request){
    return 'URL: '+request.url+'\nMethod: '+request.method+', Vars: '+request.vars+'\n\n';
}

function finalize(txt){
    log(txt);
    if(Number(local('debug'))) alert(logs);
    else if(txt) flashLong(txt);
    exit();
}

function getPage(request,func,xhr){
    if(!request.method) request.method='GET';
    if(!request.vars) request.vars=null;
    if(!xhr){
        xhr=new XMLHttpRequest();
        xhr.responseType="document";
        xhr.timeout=20*1000;
    }
    xhr.open(request.method,request.url,true);
    xhr.onload=function(){
        if(xhr.status==200 || xhr.status==401){
            log (requestToText(request)+'HTTP status: '+xhr.status+' '+xhr.statusText+'\n');
            func(xhr,request);
        } else {
            log(requestToText(request));
            finalize('Ошибка HTTP: '+xhr.status+' '+xhr.statusText);
        }
    }
    xhr.onerror=function(){
        log(requestToText(request));
        finalize('Ошибка: отсутствует соединение');
    }
    xhr.ontimeout=function(){
        log(requestToText(request));
        finalize('Ошибка: таймаут соединения');
    }
    xhr.send(request.vars);
}

logs='';
getPage({'url':local('url')},checkConn);

Скрипт в форматировании под узкий экран
function getUrl(url1,url2){
  url1=url1.split('?')[0];
  return url2.length?
    (/^http(s?):\/\//i.test(url2)?
      url2:
        (url2[0]=='/'?
        url1.split('/').slice(0,3).join('/')+url2:
        url1.split('/').slice(0,-1).join('/')+
      '/'+url2)
    ):url1;
}

function getVars(form,tag){
  vars='';
  fields=form.getElementsByTagName(
    tag);
  for(i=0;i<fields.length;i++)
    vars=vars+(i?'&':'')+fields[i].name+
      '='+fields[i].value;
  return vars;
}

function submit(xhr,request,form){
  request.url=getUrl(request.url,
    form.action);
  request.method=form.method;
  vars1=getVars(form,'input');
  vars2=getVars(form,'textarea');
  request.vars=vars1||vars2?
    (vars1?vars1:'')+
    (vars1&&vars2?'&':'')+
    (vars2?vars2:'')
    :null;
  getPage(request,processPage,xhr);
}

function processPage(xhr,request){
  redir=xhr.getResponseHeader(
    'Location');
  if(redir){
    if(redir==request.url)
      finalize('Ошибка: циклическое '+
        'перенаправление');
    else{
      log('Перенаправление\n\n');
      getPage({'url':redir},processPage,
        xhr);
    }
  } else {
    forms=local('forms').split(',');
    id=null;
    for(i=0;i<forms.length;i++)
      if(xhr.response.getElementById(
          forms[i]))
        id=forms[i];
    if(id)submit(xhr,request,
      xhr.response.getElementById(id));
    else if(Number(local('debug'))){
      log('Формы на странице:\n');
      forms=xhr.response.
        getElementsByTagName('form');
      if(forms.length)
        for(i=0;i<forms.length;i++)
          log((i?', "':'"')+forms[i].id+'"');
      else log('отсутствуют');
      finalize();
    } else finalize(
      'Аутентификация успешна');
  }
}

function checkConn(xhr,request){
  redir=xhr.getResponseHeader(
    'Location');
  if(redir){
    log('Перенаправление\n\n');
    getPage({'url':redir},processPage,
      xhr);
  } else {
    log('Аутентификация не '+
      'требуется');
    finalize();
  }
}

function log(txt){logs=logs+(txt?txt:'');}

function requestToText(request){
  return 'URL: '+request.url+
    '\nMethod: '+request.method+
    ', Vars: '+request.vars+'\n\n';
}

function finalize(txt){
  log(txt);
  if(Number(local('debug'))) alert(logs);
  else if(txt) flashLong(txt);
  exit();
}

function getPage(request,func,xhr){
  if(!request.method)
    request.method='GET';
  if(!request.vars)request.vars=null;
  if(!xhr){
    xhr=new XMLHttpRequest();
    xhr.responseType="document";
    xhr.timeout=20*1000;
  }
  xhr.open(request.method,
    request.url,true);
  xhr.onload=function(){
    if(xhr.status==200 ||
        xhr.status==401){
      log (requestToText(request)+
        'HTTP status: '+xhr.status+' '+
        xhr.statusText+'\n');
        func(xhr,request);
    } else {
      log(requestToText(request));
      finalize('Ошибка HTTP: '+
        xhr.status+' '+xhr.statusText);
    }
  }
  xhr.onerror=function(){
    log(requestToText(request));
    finalize('Ошибка: отсутствует '+
      'соединение');
  }
  xhr.ontimeout=function(){
    log(requestToText(request));
    finalize('Ошибка: таймаут '+
      'соединения');
  }
  xhr.send(request.vars);
}

logs='';
getPage({'url':local('url')},checkConn);

Набор скрипта с телефонной клавиатуры, к сожалению, не располагает к комментариям, но я вкратце опишу алгоритм:
  1. Пытаемся загрузить страницу, указанную в переменной %url
  2. Если в ответе нет HTTP-заголовка Location, значит нас не перенаправляют, а значит в данный момент аутентификация не нужна, выход
  3. Загружаем страницу, на которую нас направили.
  4. Если есть заголовок Location, возвращаемся к п.3
  5. Если на странице есть форма из списка в переменной %forms, изображаем ее submit и возвращаемся к п.3
  6. В остальных случаях — мы успешно прошли аутентификацию


После завершения ввода скрипта, у нас получилась вот такая задача:



С помощью первой иконки в нижнем ряду можно попробовать ее запустить. Теперь самое время спускаться в метро, чтобы настроить ее!

В метро, подключившись к точке доступа, пробуем запустить задачу. Если нет явных проблем с доступностью серверов, то мы увидим сообщение, похожее на то, что изображено на следующем рисунке слева. Внизу мы видим идентификатор формы, которая есть на последней загруженной странице — auth-form. Эта форма — явно наш клиент, вносим ее название в переменную %forms и запускаем задачу еще раз, получаем примерно то, что изображено на следующем рисунке в центре. Новый идентификатор формы — hidden_form. Добавляем ее в переменную %forms, теперь ее значение будет 'auth-form,hidden_form'. Запускаем задачу еще раз и видим примерно то, что изображено на следующей рисунке справа — либо будет форма без идентификатора, либо пометка «отсутствуют» (зависит от ветки метро). Если теперь запустить браузер, будет понятно, что аутентификацию мы прошли. Присваиваем переменной %debug значение «0», и закрываем задачу — тут мы закончили.



Теперь дело за малым — настроить автоматический запуск задачи при подключении к нужной точке доступа. Переходим на закладку Profiles и создаем новый профиль, который будет активироваться после подключения к точке доступа московского метро. После того, как закончим формировать описание точки доступа, Tasker спросит нас с какой задачей связывать это профиль, выбираем, естественно, Metro Auth.



Еще одни нюанс: хотя и редко, но аутентификация все таки слетает, хотя отключения от точки и не происходило. Если не было отключения, не было и переподключения, а значит Tasker не запустит задачу повторно, поэтому мы настроим Tasker так, чтобы аутентификация автоматически проверялась каждые 2 минуты (минимальный возможный интервал), для этого нам надо долгим нажатием на уже сконфигурированное условие вызвать меню, в котором добавить временное условие, в котором установить интервал.



Ну вот и все. Отныне и до тех пор, пока не придется менять идентификаторы в переменной %forms, алгоритм ваших действий при заходе в вагон следующий:
  1. Включить Wi-Fi;
  2. Дождаться сообщения «Аутентификация завершена» на экране;
  3. Загадочно улыбнуться и заняться своими делами.


UPD: По совету Self_Perfection я экспортировал проект и выложил одним файлом. Этот файл нужно скачать и положить в папку /sdcard/Tasker/projects, потом запустить Tasker, долгим нажатием на иконку домика в нижнем левом углу вызвать меню и выбрать Import. В этой версии я вынес проверку раз в две минуты в отдельный профиль — так должно расторопнее работать.

Комментарии (27)


  1. wrewolf
    20.08.2015 16:16
    +1

    В случае если хотят показать видео, данный способ тоже сработает?


    1. Hormiga
      20.08.2015 16:20
      +1

      Да.
      После нажатия «Войти в интернет», нас перенаправляют на страницу с hidden_form, которая сабмитится яваскриптом. Если видео нет, то она сабмитится после загрузки страницы, если есть, то после загрузки страницы+%продолжительность видео%.
      Этот скрипт же сабмитит ее сразу, не дожидаясь.


  1. dlinyj
    20.08.2015 16:23

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

    Однако, я всё же думал какой-то более простой метод входа, лично я не знаком с вебом и для меня эта статья выглядит немного вот так (вижу тонны минусов):



    Но на безрыбье… Спасибо за рецепт!


    1. dlinyj
      20.08.2015 16:31
      +1

      Вопрос, на момент создания переменных %url %forms %debug, какие значения им присваивать?


      1. Hormiga
        20.08.2015 16:35

        %url: любую небольшую страницу, например — «ya.ru»
        %forms: там написано, на сегодняшний день работает «auth-form,hidden_form» (без кавычек)
        %dbug: 0 — пока все хорошо, 1 — когда надо разобраться что не так.


        1. dlinyj
          20.08.2015 16:56

          Спасибо!


    1. skygad
      20.08.2015 16:39

      Так я вроде видел на страничке с приветствием предложение отключить рекламу нафиг, за какое-то пожертвование.
      Сегодня еще раз гляну.


    1. l0ser140
      21.08.2015 03:15
      +1

      То ли 100, то ли 60 рублей в месяц стоит отключение всякой рекламы.
      Необходимость жмакать на «войти в интернет», тем не менее останется.


  1. Compazavr
    20.08.2015 16:34
    +10

    Не благодарите :)
    Автовход в метро


    1. Hormiga
      20.08.2015 16:37

      Ну, получается в метро будет одним велосипедом больше =)


      1. dlinyj
        20.08.2015 16:57

        На мой взгляд изобретение всяких велосипедов — это прекрасно. Позволяет развиваться самому и мозгам.


      1. IRainman
        20.08.2015 22:47

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

        https://play.google.com/store/apps/details?id=co.uk.syslynx.wifiwebloginapp

        P.S. очень плохо, что WI-FI в метро и других общественных местах до сих пор полностью открыт и не зашифрован :( злоумышленники радуются ведь, а MitM просто на коленке даже с помощью смартфона можно осуществить. Ведь раз присылают код доступа по SMS, могут ведь и авторизацию нормальную провести в сети и огородить пользователей с помощью WPA2 и RADIUS, эх.

        P.P.S. Прошу прощения, что пишу сюда столь много


        1. ValdikSS
          02.09.2015 13:20

          Вообще говоря, есть WFA-UNAUTH-TLS — EAP-TLS без аутентификации клиента, т.е. без ключа на стороне клиента, но он нигде не поддерживается и им никто не пользуется, к сожалению.


          1. IRainman
            02.09.2015 13:34

            Благодарю, не знал. Обидно конечно.


    1. Self_Perfection
      20.08.2015 18:24

      И при наличии такого большого количества велосипедов разной степени велосипедистости почему-то нет репозитория со скриптом автовхода. Год назад кто-то делал репу с шелл однострочником, но с тех пор всё раза 3 изменилось.

      Хорошо бы какое-нибудь решение дружественное к запуску из командной строки. JavaScript в этом смысле не айс: кажется из CLI его можно запустить только с помощью node.js, который, мягко говоря, не является самым стандартным пакетом в репозиториях.


      1. Hormiga
        21.08.2015 09:34

        Выложил профиль одним файлом для импорта.


    1. zilia
      22.08.2015 01:52

      Да полно таких приложений. Вот это например вполне справляется с задачей.


      1. NetJorika
        25.08.2015 11:18

        Оно не очень хорошо ввиду того, что часто падает, да и не пропускает рекламу.


  1. Zzzuhell
    20.08.2015 16:52

    Мегаактуально!
    Жаль только, что чаще я пользуюсь в метро iPod Touch. А для него таких решений пока нет, видимо.

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


    1. Hormiga
      20.08.2015 16:56
      +1

      Во времена мистера мускула у меня все работало.
      Однако если не работает, ставим %debug=1 и смотрим что изменилось.


  1. ErgoZru
    20.08.2015 18:06

    Давно пользуюсь софтиной с 4пда, ибо таскер покупать не очень хочется, а пиратку ставить взападло) Но за этот пост плюс однозначно, особенно за дебаг режим)))


  1. Self_Perfection
    20.08.2015 18:31

    Дружище, я вообще давно ушёл с андроида, но, помнится, когда я игрался с Tasker'ом, там была возможность экспортировать/импортировать task целиком в текстовом xml представлении. Это ж куда удобнее, чем вручную task набивать.

    Оффтопик: где же ты где, кроссплатформенное (/Android / Sailfish OS / Tizen / Ubuntu Touch / ...) средство автоматизации смартфонных операций, которое позволит безболезнено мигрировать между платформами, перетаскивая с собой свой профиль автоматизаций? Эх мечты, мечты…


  1. lolipop
    20.08.2015 19:35

    Джаваскрипт(или что что это?) сложно для меня читается. Может кто-то сконвертит под айфон или на крайняк под curl/bash, благо джейлбрейк есть.


  1. DenimTornado
    20.08.2015 22:45

    А можно также, но для iOS?


    1. mukizu
      21.08.2015 08:02

      У меня почему-то автоматически авторизуется через встроенный браузер, когда ссылку из Fb пытаешься открыть, например. То есть тупо нажимаешь на ссылку на внешний ресурс в приложении Fb — и авторизация происходит сама по себе, без рекламы итп


  1. l0rda
    21.08.2015 15:05

    Все на самом деле гораздо проще. Поднимаем VPN до своего сервера по 53/UDP, и ходим в интернет в метро без всякой аутентификации.


  1. teamfighter
    24.08.2015 15:42

    На 5-м андроиде всплывает окно с уведомлением о необходимости аутентификации при подключении к WiFi. После нажатия «войти в интернет» окно пропадает. К слову сказать, я интереса ради читал правила пользования WiFi в метро, и был несколько удивлен пунктом «запрещается использование специализированных скриптов или приложений для автоматизации входа в сеть wifi».