...

суббота, 18 июля 2015 г.

Первый в истории ReactOS Hackfest

Спешим поделится важной информацией. Первому в истории ReactOS хакфесту быть! Мероприятие пройдет с 7 по 12 августа 2015 года в городе А́хен (Германия). Приглашаются все желающие.

Всю информацию о событии можно получить на специальной вики-страничке.

image
Фотография с аналогичного мероприятия GNOME WebKitGtk+ Hackfest

Город Ахен расположен в месте, где Германия смыкается с Бельгией и Нидерландами, в 4-5 км от границ с этими странами. К югу от города начинается национальный парк Эйфель. Откройте для себя наиболее западный город Германии. В историческом центре города, Аахен предлагает одновременно вкусить дух научной среды с возможностью оценить огромное разнообразие пабов. Давайте поймаем эту атмосферу и будем кодить неделю напролет, чтобы в команде добиться достойных результатов!

Свое участие уже подтвердили такие разработчики, как Алексей Брагин, Eric Kohl, Thomas Faber, Victor Martinez, Amine Khaldi, Daniel Reimer, Stefan Ginsberg.

Список участников и идей для хакфеста постоянно обновляется на второй вики-страничке.

Участники самостоятельно оплачивают свой проезд и проживание. Однако, в исключительных случаях, несвязанных непосредственно с поездкой на хакфест, Фонд может частично или полностью покрыть издержки участника, что подлежит индивидуальному обсуждению.

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

image
Мы уже готовимся к встрече с Вами!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] Стартап-школа: Как проводить интервью с пользователями


Cтэнфордский курс CS183B: How to start a startup. Стартовал в 2012 году под руководством Питера Тиля. Осенью 2014 года прошла новая серия лекций ведущих предпринимателей и экспертов Y Combinator:

[ 1-3 ] Стартап и его команда: Y Combinator и Facebook

[ 4-7 ] Growth Hacking: Homejoy, PayPal, Palantir и Wufoo

[ 8 ] Масштабирование: DoorDash, Teespring, KiKo и Justin.tv

[ 9 ] Как привлечь инвестиции: Andreessen Horowitz, Ron Conway и Zenefits

[ 10-11 ] Культура компании: AirBnB, Sequoia Capital, Pinterest и Stripe

[ 12 ] Разработка B2B-продуктов: Box

[ 13-15 ] О руководстве и сотрудниках: LinkedIn, Khosla Ventures, Scribd, Andreessen Horowitz

[ 16 ] Работа с пользователями: Twitch

Сэм Альтман (Sam Altman): Добрый день. Наш сегодняшний гость – Эммет Шир. Эммет – генеральный директор сервиса Twitch, приобретенного компанией Amazon, в которой он сейчас работает. Эммет расскажет о том, как правильно проводить интервью с пользователями. Эта часть курса «Как основать стартап» будет посвящена общению с клиентами – должно быть по-настоящему полезно. Спасибо большое, что навестил нас.

Эммет Шир: Спасибо, Сэм. Я основал свой первый стартап с Джастином Каном (Justin Kan) еще в колледже. Мы назвали свою компанию Kiko Calendar. Но дела пошли не очень успешно. Вообще-то, все было не так уж плохо. Мы построили компанию и продали ее на eBay. Пусть это и не тот результат, которого вы, вероятно, надеетесь получить от своих стартапов.


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

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


У нас появилась идея о создании телешоу Justin.tv, а именно – реалити-шоу о жизни Джастина Кана. Мы разработали технологию и веб-сайт этого реалити-шоу. Мы сами были пользователями своего продукта. Один из способов схитрить и избежать разговоров со многими другими клиентами – это создать что-то только для себя в буквальном смысле. Таким образом, вам не придется обсуждать проект с кем-либо еще, потому что вы знаете, чего хотите и что вам нужно. Но такой прием подходит далеко не всем стартапам. В большинстве случаев [к сожалению] стартапы создаются не для тех, кто ими пользуется. Изредка при создании такого стартапа удача может вам улыбнуться, и вы окажетесь одним из многих людей, которые тоже захотят пользоваться тем, что используете вы. Но чаще всего, такие проекты отходят на второй план и ни к чему не приводят. 


Какое-то время мы продолжали работать над проектом Justin.tv и действительно преуспели, поскольку, как оказалось, нашлись люди, которые тоже хотели заниматься этим – транслировать свою жизнь через интернет. Единственное, что мешало Justin.tv достичь небывалых высот – то, что мы не могли понять, как выйти за рамки этого первоначального телешоу. Мы создали замечательный продукт. Если вы хотели запустить круглосуточное реалити-шоу о своей жизни, то наш сайт для вас. У нас было как раз то, что вам нужно, но мы хотели большего. Мы хотели преумножить количество пользователей и увеличить возможности нашего продукта, но не имели представления, как это сделать, ведь мы не были этими пользователями.

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

Поднимите руки, кто из присутствующих знает о просмотре видеоигр в интернете? Если вы не в курсе, вам следует почитать об этом, чтобы понять, о чем я буду говорить. Я думал, что идея потрясающая, но ничего не знал об одном важном аспекте, а именно о приобретении контента, который будет транслировать стартап. Мы провели большое количество интервью с пользователями. Мы опросили многих людей, и полученные данные легли в основу всех принимаемых решений касательно технических характеристик Twitch на протяжении следующих трех лет. Мы продолжали говорить с пользователями, более того, целое отделение нашей компании занималось таким общением. У нас не было такого подразделения на Justin.tv. Никто в нашей компании не отвечал за общение с наиболее важными пользователями.

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

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


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

А теперь я хочу кое-что сделать в интерактивном режиме. Вы предложили несколько идей, и я выбрал одну из них. Я хочу, чтобы вы сделали первый шаг этого процесса [изучения пользователей] прямо сейчас. Кого бы вы стали опрашивать, куда бы вы пошли, чтобы выяснить, что вам нужно создать? Идея, которую я выбрал – это приложение для конспектирования лекций.

Идея такова: уровень техники конспектирования пока далек от совершенства, и я хочу создать приложение для конспектирования, которое повысит его. С помощью такого приложения будет легче делать конспекты на занятиях. Может быть, в нем появятся возможности для коллективной работы, или оно каким-то образом повысит вашу концентрацию. А может, оно будет расширять мультимедийные возможности [конспекта лекций].

Допустимы любые характеристики. В этом заключается идея. Итак, у вас есть 120 секунд, чтобы подумать не о том, какие вопросы задать или какие свойства необходимы этому приложению, а о том, с кем вы будете говорить. Чей ответ поможет вам понять, стоит ли заниматься этим или нет? Будет лучше, если вы не просто подумаете об этом, но и запишете пять типов людей, с которыми вы бы поговорили: укажите, кто из них, по вашему мнению, был бы вам наиболее важен.

Нет ничего лучше, чем попробовать что-то на собственном опыте. На самом деле, лучший способ это сделать – подумать. Я рад слышать стук клавиатуры. Если вы слушаете лекцию из дома – подумайте об этом тоже. Первый вопрос при запуске почти любого стартапа – с кем поговорить. Вам нужен ответ на вопрос: кто мой пользователь, и где я могу его найти?

Я попрошу одного добровольца из зала подняться и рассказать нам, с кем бы он поговорил. И мы обсудим это.

Студент 1: В первую очередь, я бы определенно поговорил со студентами из колледжа, потому что мы посещаем кучу лекций. В частности, я бы поговорил со студентами, изучающими разные предметы. Чтобы увидеть разницу в технике конспектирования между теми, кто изучает английский и теми, кто изучает математику или информатику.

Эммет Шир: Значит, ты хочешь поговорить со студентами из колледжа. Можешь выбрать конкретную группу студентов? Ведь мы не будем опрашивать всех студентов в колледже.

Студент 1: Я хочу поговорить со студентами из колледжа и потом разбить их по группам в зависимости от изучаемых дисциплин. Это было бы целесообразным для людей, применяющих разные техники при обучении, так как некоторые пишут много конспектов. Другие не пишут подробные конспекты, но все равно делают какие-то заметки.

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

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

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

Но после этого вам стоит поговорить и с другими группами. Я бы поговорил также с программистами. Это еще одна достаточно перспективная категория. Вы можете поговорить с родителями.

Кто тратит деньги на образование своих детей? Кто готов раскрыть свой кошелек? Родители первокурсников, чьи дети впервые идут в колледж. Вашему ребенку нужно это приложение для успеваемости, чтобы не вылететь из колледжа. Существует множество таких групп людей, которые не являются непосредственными пользователями вашего продукта, но играют потенциально решающую роль в его успехе. Когда ваш стартап находится на начальной стадии, когда у вас только появляется эта потрясающая идея, вам хочется охватить настолько широкую аудиторию, насколько это возможно. Вы не хотите ограничиваться лишь одной категорией людей. Вам хочется узнать о всех, кто может внести свой вклад.

Давайте пригласим кого-нибудь сюда и притворимся, что проводим интервью с пользователем. Мы поговорим со студентом колледжа и выясним, что нам нужно создать, что нужно добавить в это приложение для конспектирования. Так, нужен еще один волонтер для интервью. Привет.

Студент 2: Привет. Меня зовут Стефани.

Эммет: Привет, Стефани.

Стефани: Приятно познакомиться.

Эммет: Спасибо, что согласилась поучаствовать в интервью. Я бы хотел узнать о твоем способе конспектирования. Как ты делала заметки сегодня?


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

Эммет: Так чем же ты пользуешься сегодня?

Стефани: Просто ручкой и бумагой.

Эммет: Ты используешь ручку и бумагу. Значит ты комбинируешь. Ты делаешь заметки ручкой, а иногда пользуешься компьютером.

Стефани: Да.

Эммет: Когда ты делаешь все эти заметки, ты просматриваешь их в конце? Только честно! Действительно ли ты возвращаешься к ним?

Стефани: Конспекты, написанные ручкой – не особо. Но я просматриваю те, что веду на компьютере. Их проще найти, ими проще делиться с одногруппниками и друзьями, сотрудничать.

Эммет: Какими программами ты пользовалась, чтобы делать конспект сегодня?
Audience Member 2: Google Docs and Evernote.

Стефани: Google Docs и Evernote.

Эммет: А почему сразу двумя?

Стефани: Если я просто делаю записи для себя, то проще использовать Evernote. Такими заметками можно делиться, но для этого больше подходит Google Docs. Если мой друг уже создал папку в Google Docs, мне нужно просто добавить документ в эту папку, когда, например, мы делаем групповой проект. Если же это для моего личного пользования, тогда я предпочитаю Evernote.

Эммет: Кажется, ты активно взаимодействуешь с другими ребятами.

Стефани: Да, я хочу объединить наши усилия.

Эммет: Расскажи поподробнее. Стараешься ли ты собрать как можно больше информации из конспектов других людей? Или ты в основном просматриваешь свои записи в конце семестра? Как это происходит?

Стефани: В основном я читаю собственные конспекты, потому что я достаточно придирчива к оформлению. Я уделаю внимание дизайну, форматированию, даже цвету. Шрифт, который мы используем, очень влияет на то, как я учусь. Так что я стараюсь персонализировать свои конспекты, даже после объединения с чужими.

Эммет: Значит ты берешь конспекты других людей, но затем переделываешь их по-своему. Потрясающе! Если у тебя есть конспекты в Evernote, и Google Docs, и написанные на бумаге от руки возвращаешься ли ты к ним снова, когда семестр заканчивается? Ты просматриваешь свои конспекты по окончани семестра?

Стефани: Конспекты, сделанные на занятиях, я не перечитываю. Но если речь идет о семинарах, таких как этот, или подготовке к интервью, то обычно я возвращаюсь к ним время от времени, чтобы освежить знания. Это помогает мне готовиться к предстоящим событиям.

Эммет: Интересно. Будь добра, расскажи об этом. Ты делаешь конспекты не только на занятиях?

Стефани: Я делаю конспекты, чтобы выделить ключевые моменты. Например, вдохновляющие цитаты из семинаров, таких, как этот. Если я собираюсь на встречу с кем-то, то записи помогут мне запомнить, о чем шла речь.

Эммет: Здорово. При других обстоятельствах я бы углубился в этот вопрос. У меня все еще осталось большое количество открытых вопросов после этого разговора. С кем ты сотрудничаешь? Насколько длинные твои конспекты? Сколько времени ты тратишь на конспектирование? Я бы подробнее рассмотрел все эти аспекты, но чтобы не тратить ваше время и не останавливаться на одном примере, мы будем двигаться дальше. Большое спасибо, Стефани.

Стефани: Вам спасибо.

Эммет: Вы заметили, что мы вообще не обсуждаем содержание приложения. Меня не особо интересуют его свойства. Мне неинтересно, какой конкретно набор функций есть в Google Docs или Evernote. Возможно, я подробнее остановлюсь на тех функциях [этих приложений], которыми мы на самом деле пользуемся. Если Стефани постоянно обменивается информацией, как это происходит? Я услышал интересную вещь: «Мы используем папку».

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

Поэтому старайтесь держаться от обсуждения функционала как можно дальше, так как большинство из того, что клиенты скажут [на эту тему], покажется вам реализуемым. Когда реальный пользователь просит вас создать определенную функцию, очень сложно отказать, потому что перед вами человек, у которого и в самом деле есть эта проблема. Они говорят: «Сделайте мне эту функцию». Но только опросив множество других людей и получив представление об их проблемах, вы выясните, действительно ли это перспективная область или нет.

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

Это не значит, что вы не можете двигаться дальше и продолжать опрашивать все больше людей. Тот факт, что первое интервью не принесло результата, не значит, что это же произойдет в дальнейшем. Как только вы опросите 6-8 человек, вы обычно останавливаетесь на этом. При таком подходе вы едва ли соберете достаточное количество необходимой информации. Поэтому так важно поговорить с разными людьми, чьи ответы будут различными. Потому что ответы шести студентов Стэндфордского Университета будут отличаться от ответов шести учеников старшей школы или шести родителей.

Исходя из этого, у вас, возможно, возникло несколько идей. Вы получили информацию о том, как кто-то делает конспекты. У вас появились мысли насчет того, как создать нечто классное. Если бы вам нужно было добавить всего одну характеристику в Google Docs, что бы это было? Для нового продукта вроде того, о котором мы говорим, это – хороший способ развиваться. Как мы можем хоть немного улучшить эту программу, которая так активно используется сейчас? Что действительно заинтересует пользователя, что даст вам преимущество?

Подумайте пару минут, что за функционал это может быть. Исходя из рассказа Стефани, постарайтесь придумать что-то, что убедит ее отказаться от привычной коллективной работы в Google Docs в пользу вашего нового приложения с теми же характеристиками, что и Google Docs, плюс с вашей новой характеристикой. Это должна быть особая полезная функция, которая убедит пользователей прекратить работать с тем, чем они пользуются сейчас.

Студент 3: Причина, по которой она пользуется Evernote, это краткие заметки. С их помощью удобнее записывать какие-то отдельные мысли. Мне кажется, что в Google Docs нет документов такого небольшого формата. Я думаю, было бы полезно создать мобильную версию приложения, которая не будет такой громоздкой.

Эммет Шир: Отлично. Хорошее наблюдение. Это как раз то, что вы узнаёте из интервью с пользователями. Теперь у вас есть эта идея. Вы получили ответ от пользователя. Что если бы в Google Docs были возможности не только для совместной и групповой работы, но и вот такие небольшие заметки? Появился бы продукт, который больше рассчитан на краткие записи. Возникает следующий вопрос: достаточно ли вам этой идеи? Действительно ли люди клюнут на это преимущество?

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

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

Сэм Альтман: Можно ли получить хоть какие-то гарантии прежде, чем приступать к созданию функции?

Эммет Шир: Что же можно сделать, чтобы подстраховаться, учитывая, что вы не можете просто прийти и спросить у клиентов: «Полезна эта функция или нет?» Это зависит от конкретной функции. Обычно лучшее, что вы можете сделать – это быстро создать прототип. Если вы хотите создать приложение на основе Google Docs, не пытайтесь делать Google Documents, но для конспектирования. Попытайтесь написать расширение для браузера, которое реализует эту небольшую функцию, и посмотрите, будет ли она полезной для пользователей. Найти способ схитрить – вот что вам нужно сделать, потому что очень сложно узнать, насколько хороша та или иная функция, если у вас нет возможности показать её людям.

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

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

Последнее, что мне хочется делать, это еще раз пройти через то, что случилось в Twitch. Я принес несколько слайдов, которые я бы хотел показать вам. Здесь представлены примеры отзывов пользователей Twitch. Изначально в документе было 26 страниц, но я решил, что читать все отзывы будет слегка утомительно, кроме того, я не уложусь по времени в одну лекцию. Поэтому я предварительно сократил их объем для вас.

Перед запуском Twitch мы поговорили с некоторыми стримерами на Justin.tv и задали им вопросы о транслировании. Что им нравится в процессе транслирования, почему они занимаются этим, о чем они обычно рассказывают и чем еще они занимаются помимо транслирования. Если вы поговорите с пользователями своего продукта, то получите от них очень подробную информацию о функциях, так как они напрямую пользуются ими. Тут нужно читать между строк. К нам обращались с просьбами о возможности очистить черный список в чате. Это довольно обычный запрос, потому что наш чат работал особым образом. Пользователи просили нас создать возможность редактирования уже готовых заголовков. И эти просьбы были действительно обоснованы.

Мы опросили около 12-14 стримеров с игрового направления на Justin.tv и получили все эти отзывы. «У ваших конкурентов есть все эти крутые «фичи», такие как опросы и скролл. А еще в их программе я могу менять настройки в чате». Позже мы получили несколько положительных отзывов. «Да, у вас нет этой назойливой рекламы и это круто. И можно «банить» троллей». Некоторые замечания были связаны с чатом, общением со зрителями. Это было действительно интересно. Вот, чего хотели от нас стримеры Justin.tv.

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


А теперь мы рассмотрим отзывы об одном из наших конкурентов, что тоже очень интересно. Вот, о чем говорят стримеры, использующие другие платформы трансляции. Они хотели иметь возможность подключения к своему каналу сразу нескольких зрителей враз. Они жаловались на отсутствие программы revshare (прим. система, предусматривающая выплату процента от прибыли). Они часто говорили, что пытаются подзаработать и очень бы хотели получать деньги на трансляции игр. Они говорили о стабильности видеоканала.

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

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

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

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

Если вы только и делаете, что наблюдаете за конкурентами и разговариваете с их пользователями, вам никогда не удастся расширить свою аудиторию. Таким образом вы никогда не получите полезную информацию. Вам нужно поговорить с теми, кто вообще никогда не пользовался подобными программами. Возможно, они хотели, но пока еще не успели.

Что они сказали? «Мой компьютер работает недостаточно быстро». «Я по 12 часов в день тренируюсь для следующего турнира». «Мне нравится делать идеальные видеоролики и редактировать их. Я залил пару таких на YouTube». «Мне не нравятся онлайн-стримы, и у меня нет желания осваивать эту сферу». «В Корее с этим сложно. Как только наша стратегия игры «утекает» в Сеть, нам приходится придумывать новую. Последнее, что мы хотели бы показывать – это наши тренировки. О чем вы?! Это бы навредило нам в следующих чемпионатах».

Мы начали серьезно работать с людьми, пытались понять, как сделать так, чтобы они влились в стриминговое сообщество. Мы покупали людям компьютеры. Мы стали тесно сотрудничать и оказывать поддержку компаниям по разработке программного обеспечения для видеотранслирования. Мы добавили функцию бродкаста в игры и платформы, а также в Xbox и PlayStation4, так как нам нужно было решить проблему отсутствия возможности транслирования. Вот три основных направления, в которых мы планировали развивать этот функционал.

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

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

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

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

На Justin.tv мы изучали огромное количество информации, тратили кучу времени, копаясь в Google Analytics, Mixpanel и корпоративных аналитических инструментах. Мы старались понять, как люди используют наш сервис, откуда идет трафик, степень завершения тех или иных сценариев. Эти инструменты тоже могут оказаться полезны.

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

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Вставка в середину: ArrayList против LinkedList

Как-то на собеседовании мне задали вопрос: какая реализация списка выполнит вставку в середину быстрее: ArrayList или LinkedList? С первого взгляда вопрос простой — нужно посчитать алгоритмическую сложность каждого варианта и сравнить их. Вставку в середину можно разделить на две операции: поиск середины списка и саму вставку. Для ArrayList поиск элемента по индексу не составляет труда, так как элементы списка находятся в массиве. Алгоритмическая сложность составляет O(1). В случае LinkedList придётся последовательно перебрать все элементы, пока не доберёмся до нужного элемента. Сложность будет O(n). Вставка в ArrayList связана со сдвигом всех элементов, находящихся после точки вставки, поэтому алгоритмическая сложность этой операции O(n). В LinkedList вставка заключается в создании нового связующего объекта и установки ссылок на него у соседних элементов. Сложность O(1). В сумме сложность вставки в середину у ArrayList и у LinkedList получается одинаковая — O(n). Эти рассуждения я показал интервьюеру, на что он мне задал вопрос: «Так что всё-таки быстрее: пробежаться по элементам или сместить элементы?». Я быстро прикинув, что операция чтения по сути быстрее операции записи, сказал, что LinkedList справится быстрее.
Когда я пришёл домой, я задумался над этим вопросом. Поискал решение этой задачи на форумах. Кто-то был согласен со мной, но многие учли, что операция смещения производится native методом, который работает быстрее, поэтому ArrayList выполнит вставку в середину быстрее. Не получив окончательного и неопровержимого ответа, я решил провести собственное исследование.

Сперва я углубился в изучение исходного кода этих классов. Вставка элемента в ArrayList проходит так: сначала, при необходимости, массив увеличивается, затем все элементы, стоящие после места вставки сдвигаются на число позиций, равное количеству вставляемых элементов (я рассматриваю один элемент), и в конце встаёт на свое место вставляемый элемент. Массив увеличивается со скоростью n*1,5, где n — размер массива, а минимальное увеличение при стандартных условиях (capacity=10) — 5 элементов. В связи с этим, операцией по увеличению массива при расчёте алгоритмической сложности вставки можно пренебречь. Сдвиг элементов, находящихся после точки вставки обладает алгоритмической сложностью O(n). Таким образом общая сложность всё равно остаётся O(n). Да, мы держим в уме, что операция увеличения массива незначительно повышает сложность, но нативность действий с массивом увеличивает скорость работы.

Поиск элемента в LinkedList начинается в цикле от края списка. Если искомый элемент находится в первой половине списка, то поиск идёт с начала, в обратном случае — с конца. Так как мы вставляем именно в середину, то в цикле пройдём ровно половину элементов. Сложность O(n). Сама вставка, как я уже писал выше, заключается в создании объекта и указании ссылок на него. Сложность O(1). Опять же ничего нового я не выяснил: общая сложность осталась O(n), при этом держим в уме, что создание объекта — «дорогая» операция.

Анализ исходного кода ситуацию не разъяснил, поэтому стал писать тесты. Я решил исследовать зависимость от двух параметров: начальный размер списка и количество вставок подряд (количество итераций).

Пример исходного кода
package com.jonasasx.liststest;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Main {

        private static final int        MAX_SIZE                = 1000;
        private static final int        MAX_ITERATIONS  = 10000;
        private static final float      STEP_SIZE               = 2f;
        private static final float      STEP_ITERATIONS = 5;
        private static final int        TESTS_COUNT             = 100;

        public static void main(String[] args) throws InterruptedException {
                ArrayList<String> arrayList;
                LinkedList<String> linkedList;
                for (float size = 1; size < MAX_SIZE; size *= STEP_SIZE) {
                        for (float iterations = 1; iterations < MAX_ITERATIONS; iterations *= STEP_ITERATIONS) {
                                double sum = 0;
                                for (int k = 0; k < TESTS_COUNT; k++) {
                                        arrayList = new ArrayList<>();
                                        linkedList = new LinkedList<>();
                                        fillList(arrayList, (int) size);
                                        fillList(linkedList, (int) size);
                                        sum += Math.log10(calculateRatio(arrayList, linkedList, (int) iterations));
                                }
                                double logRatio = sum / TESTS_COUNT;
                                System.out.println(String.format("%07d\t%07d\t%07.2f\t%s", (int) size, (int) iterations, logRatio, logRatio > 0 ? "Linked" : "Array"));
                        }
                        System.out.println();
                }
        }

        static void fillList(List<String> list, int size) {
                for (int i = 0; i < size; i++) {
                        list.add("0");
                }
        }

        static double calculateRatio(ArrayList<String> arrayList, LinkedList<String> linkedList, int iterations) {
                long l1 = calculateAL(arrayList, iterations);
                long l2 = calculateLL(linkedList, iterations);
                if (l1 == 0 || l2 == 0)
                        throw new java.lang.IllegalStateException();
                return (double) l1 / (double) l2;
        }

        static long calculateAL(ArrayList<String> list, int m) {
                long t = System.nanoTime();
                for (int i = 0; i < m; i++) {
                        list.add(list.size() / 2, "0");
                }
                return System.nanoTime() - t;
        }

        static long calculateLL(LinkedList<String> list, int m) {
                long t = System.nanoTime();
                for (int i = 0; i < m; i++) {
                        list.add(list.size() / 2, "0");
                }
                return System.nanoTime() - t;
        }
}



Для каждого размера списка и количества итераций создаются по одному массиву ArrayList и LinkedList, они заполняются одинаковыми объектами, после чего под замер скорости производится n вставок одного объекта в середину. В качестве сравниваемой величины я использую десятичный логарифм от отношения времён выполнения ArrayList к LinkedList. Когда это значение меньше нуля, ArrayList справляется быстрее, когда больше — быстрее LinkedList.

Привожу результаты теста в таблице:

  Итераций                    
Размер   1 2 4 8 16 32 64 128 256 512
  1 -0,12 0,01 -0,04 0,01 0,03 -0,04 -0,09 -0,19 -0,21 -0,31
  5 -0,14 0,02 -0,02 0,07 -0,08 0 -0,15 -0,31 -0,42 -0,52
  25 -0,12 0,2 0,19 0,13 0,07 0,04 -0,1 -0,29 -0,47 -0,56
  125 -0,31 -0,01 0,01 0 -0,03 -0,11 -0,17 -0,35 -0,48 -0,57
  625 -0,47 -0,49 -0,48 -0,45 -0,49 -0,51 -0,53 -0,6 -0,67 -0,78

И в графике:

image
На оси X указан изначальный размер списка, линии представляют разные количества итераций, а на оси Y отношение скорости работы двух реализаций списка.

Так как с каждой итерацией размер списка увеличивается, можно сделать общий вывод: LinkList работает быстрее при небольшом размере списка. Но самое главное: без чёткой конкретизации условий сравнивать скорость работы этих двух алгоритмов нельзя!

Чтобы увеличить точность, я отказался от параметра количества вставок и уменьшил шаг размера до одного. Для каждого из размеров я провёл тест тысячу раз и взял среднее значение. Получил график:
image
На графике ярко выражены всплески. Они находятся точно на тех местах, где ArrayList производит увеличение массива. Следовательно, можно сказать, что пренебрегать этой операцией нельзя, как я посчитал в начале, анализируя исходный код.

Общий вывод можно сделать такой: операция вставки в середину происходит в основном быстрее в ArrayList, но не всегда. С теоретической точки зрения нельзя однозначно сравнить скорость этих двух алгоритмов без учёта изначального размера списка.

Спасибо за внимание, надеюсь, кому-то моя работа покажется интересной и/или полезной.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Настройка репликации в Mysql 5.6

После выхода mysql 5.6 с его GTID (global transaction identifier) репликация в mysql перестала быть кошмарным сном сисадмина и стала вполне рабочим инструментом. В инете есть некоторое количество информации по этому поводу, но вся она довольно разрозненная и не всегда доступна для понимания. По этому я решил сделать небольшую инструкцию-выжимку, больше для себя, но может и еще кому пригодится.


Настройка мастера
У меня довольно тепличные условия, и база пустая, дамп будем вливать после настройки

my.cnf

binlog-format=ROW

Бывает трех видов — STATEMENT, MIXED и ROW
В двух словах — statement пишет в бинлог по сути sql запросы. Преимущества — старый формат, оттестированный, лог небольшой, можно посмотреть запросы. Недостатки — проблемы с функциями и триггерами, запросы вида update user set a=1 order by rand(), а так же еще некоторые могут некорректно отрабатываться. ROW если совсем упрощенно — пишет в логи измененные бинарные данные. Преимущества — отлично логируются все виды запросов. Недостатки — огромный лог.Ну и mixed — промежуточный формат, который старается использовать statement, когда возможно, а когда нет — row. Говорят, что глючит на каких то очень сложных запросах. Именно его я и рискнул использовать

binlog-checksum=crc32
Новая фича mysql5.6, вроде как ускоряет работу бинлога

gtid-mode=on
Собственно и включает ту самую GTID mode репликацию

enforce-gtid-consistency=true
Запрещает все, что может поломать транзакции.

log-slave-updates=true
В родной документации написано: указывает подчиненному серверу, чтобы тот вел записи об обновлениях, происходящих на подчиненном сервере, в двоичном журнале. По умолчанию эта опция выключена. Ее следует включить, если требуется организовать подчиненные серверы в гирляндную цепь.

server-id = 1
Уникальный номер для каждого сервера

ну и не забываем указать что именно мы будем реплицировать —
replicate-do-db = mybase
replicate-do-table=mybase.mytable1
replicate-do-table=mybase.mytable2

После этого необходимо создать пользователя mysql с правами репликации. Например так GRANT replication slave ON *.* TO «replication»@'192.168.1.102' IDENTIFIED BY 'password';

На этом настройка мастера закончена. Вливаем дамп и в бой )

Настройка слейва

В простейшем варианте на слейв можно скопировать тот же конфиг, что и на мастере, единственное что — нужно сменить server_id, например на 2.

Перезапускаем слейв, и запускаем репликацию

change master to master_host='192.168.1.1", master_auto_position=1, Master_User=’replication’, master_password=’password';
start slave;

и любуемся

show slave status \G

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 17 июля 2015 г.

Диагностика почтовых протоколов

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

Материал разбит следующим образом:

1. Введение
2. Примеры сессий
3. Проверка авторизации на сервере(LOGIN, PLAIN, CRAM-MD5), Base64
4. Проверка шифрования SSL/TLS
5. Анализ почтового трафика при помощи tshark. Расшифровка SSL/TLS
6. Ссылки на материалы


В сети достаточно материалов по отдельным пунктам, но все разбросано по разным местам и, когда возникает необходимость выполнить ту или иную операцию, приходится по разным ресурсам вспоминать нюансы авторизации, способы быстрой кодировки в base64, ключи к openssl и tshark. Здесь все собрано вместе, а также добавлена информация о дешифровке SSL/TLS трафика.

Обозначения


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

# — приглашение в рутовом шелле, указанная после него команда выполняется с правами администратора

## — строка с комментарием

Запрос клиента в почтовых сессиях выделен жирным шрифтом.

Почтовые порты


Основные порты, использующиеся в работе почтовых серверов по RFC (документы, регламентирующие работу сети интернет и ее основных компонентов):
SMTP

  • 25/tcp SMTP (стандартный порт)
  • 465/tcp SMTPS(устаревший)
  • 587/tcp submission (порт для обслуживания клиентов)
POP3

  • 110/tcp POP3 (стандартный порт)
  • 995/tcp POP3S (порт с предварительной установкой SSL/TLS соединения)
IMAP

  • 143/tcp IMAP (стандартный порт)
  • 993/tcp IMAPS (порт с предварительной установкой SSL/TLS соединения)

Здесь перечислены только основные, помимо них разные реализации серверов могут использовать другие порты для своих служебных целей, для пользовательского и административного веб-интерфейса, общения узлов кластера и т.д.

Используемые и рекомендуемые утилиты


В статье используются telnet, openssl, tshark. Для наглядности взаимодействия сервера и клиента, использования команд протокола. На регулярной основе и для автоматизации каких-то процессов можно использовать утилиты, которые скрывают от нас все эти детали, но которые проще включаются в скрипты. Из таких утилит могу порекомендовать скрипт на perl smtp-cli, (http://ift.tt/1CLO8bm) обладающий широкой функциональностью, в том числе и возможностью SMTP авторизации. Также рекомендую утилиту imtest из состава cyrus-clients, которой можно протестировать IMAP протокол. smtp-sink, утилиту из состава postfix, которая эмулирует почтовый сервер. С ее помощью можно отлаживать работу почтового клиента в том случае, если нет ни доступа к существующим почтовым серверам, ни возможности включения в настройках клиента подробного журналирования.

При помощи nmap можно быстро проверить, доступны ли порты снаружи, то есть, слушаются ли они программами и не закрыты ли при этом файерволом:

# nmap -v -p25,110,143,465,587,993,995 127.0.0.1

Starting Nmap 4.11 ( http://ift.tt/1aFZ3Qw ) at 2014-10-31 15:59 MSK
Initiating SYN Stealth Scan against localhost.localdomain (127.0.0.1) [7 ports] at 15:59
Discovered open port 25/tcp on 127.0.0.1
Discovered open port 465/tcp on 127.0.0.1
Discovered open port 143/tcp on 127.0.0.1
Discovered open port 993/tcp on 127.0.0.1
The SYN Stealth Scan took 0.00s to scan 7 total ports.
Host localhost.localdomain (127.0.0.1) appears to be up ... good.
Interesting ports on localhost.localdomain (127.0.0.1):
PORT    STATE  SERVICE
25/tcp  open   smtp
110/tcp closed pop3
143/tcp open   imap
465/tcp open   smtps
587/tcp closed submission
993/tcp open   imaps
995/tcp closed pop3s


Nmap finished: 1 IP address (1 host up) scanned in 0.004 seconds
Raw packets sent: 7 (308B) | Rcvd: 17 (724B)


По этому выводу видно, что на сервере доступны SMTP/IMAP порты, но недоступны порты для
POP3 протокола.

Через netstat можно посмотреть не только прослушиваемые и используемые порты, как часто предполагают, но и процессы, связанные с этими портами. Вот вывод netstat для этого же почтового сервера:

# netstat -lnpvut (и -anpvut, если необходимо посмотреть текущие соединения по портам)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name
tcp        0      0 0.0.0.0:143                 0.0.0.0:*                   LISTEN      477/dovecot
tcp        0      0 0.0.0.0:2000                0.0.0.0:*                   LISTEN      477/dovecot
tcp        0      0 0.0.0.0:465                 0.0.0.0:*                   LISTEN      603/master
tcp        0      0 127.0.0.1:53                0.0.0.0:*                   LISTEN      430/unbound
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      10042/sshd
tcp        0      0 0.0.0.0:25                  0.0.0.0:*                   LISTEN      603/master
tcp        0      0 0.0.0.0:1025                0.0.0.0:*                   LISTEN      603/master
tcp        0      0 0.0.0.0:993                 0.0.0.0:*                   LISTEN      477/dovecot
tcp        0      0 127.0.0.1:1953              0.0.0.0:*                   LISTEN      430/unbound
tcp        0      0 127.0.0.1:1026              0.0.0.0:*                   LISTEN      603/master
tcp        0      0 127.0.0.1:2025              0.0.0.0:*                   LISTEN      603/master
tcp        0      0 :::22                       :::*                        LISTEN      10042/sshd
udp        0      0 127.0.0.1:53                0.0.0.0:*                               430/unbound


В этом примере в качестве SMTP сервера используется postfix и dovecot в качестве IMAP. POP3 в списке отсутствует, так как в настройках dovecot этот протокол отключен, как неиспользуемый.

В современных дистрибутивах пакет net-tools уже часто не ставится, считается устаревшим. В качестве замены испольуется утилита ss из состава iproute. Это более узко заточенная и в свой области, вероятно, более функциональная утилита с возможностью настройки фильтров как в tcpdump/tshark. Но мне, например, не нравится, как у нее отформатирован вывод информации. Чтобы чуть это исправить, можно использовать sed:

# ss -lntp | sed -r 's/\t/ /g'
Recv-Q Send-Q             Local Address:Port               Peer Address:Port
0      0                              *:143                           *:*      users:(("dovecot",477,6),("imap-login",14400,4),("imap-login",15370,4),("imap-login",15372,4))
0      0                              *:2000                          *:*      users:(("dovecot",477,8),("managesieve-log",10229,4),("managesieve-log",10230,4),("managesieve-log",21149,4))
0      0                              *:465                           *:*      users:(("master",603,31))
0      0                      127.0.0.1:53                            *:*      users:(("unbound",430,4))
0      0                              *:22                            *:*      users:(("sshd",10042,4))
0      0                              *:25                            *:*      users:(("master",603,19))
0      0                              *:1025                          *:*      users:(("master",603,12))
0      0                              *:993                           *:*      users:(("dovecot",477,7),("imap-login",14400,5),("imap-login",15370,5),("imap-login",15372,5))
0      0                      127.0.0.1:1953                          *:*      users:(("unbound",430,5))
0      0                      127.0.0.1:1026                          *:*      users:(("master",603,16))
0      0                      127.0.0.1:2025                          *:*      users:(("master",603,28))
0      0                             :::22                           :::*      users:(("sshd",10042,3))


*) для удобства использования можно поместить следующую bash функцию в ~/.bashrc
ss() { /sbin/ss $@ | sed -r 's/\t/ /g'; }


Здесь приведены примеры сессий по SMTP/IMAP/POP3 протоколам. Для соединения используется клиент телнет, который либо в системе установлен по-умолчанию, либо устанавливается из репозиториев:
Debian/Ubuntu
# apt-cache search telnet
# apt-get install telnet


RHEL/CentOS/Fedora
# yum search telnet
# yum install telnet

Вводимые команды в тексте выделены жирным шрифтом.

SMTP
$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 mailserver at mail.server.net greets you. Make love not war!

HELO localhost.localdomain
250 mail.server.net

MAIL FROM:<>
250 2.1.0 Ok

RCPT TO:<user@mail.server.net>
250 2.1.5 Ok

DATA
354 End data with <CR><LF>.<CR><LF>
FROM: root@localhost.localdomain
TO: user@mail.server.net
SUBJECT: test mail from test subject

test body


.
250 2.0.0 Ok: queued as 1CF5FC0AAE
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

IMAP
$ telnet 127.0.0.1 143
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
* OK IMAP Server at mail.server.net ready

001 LOGIN user@mail.server.net testpass
001 OK completed

002 CAPABILITY
* CAPABILITY IMAP4 IMAP4REV1 ACL NAMESPACE UIDPLUS IDLE LITERAL+ QUOTA ID MULTIAPPEND LISTEXT CHILDREN BINARY LOGIN-REFERRALS STARTTLS AUTH=LOGIN AUTH=PLAIN AUTH=CRAM-MD5 AUTH=DIGEST-MD5 AUTH=MSN
002 OK completed

003 SELECT Inbox
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $MDNSent)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $MDNSent)] limited
* 7214 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 306349424] UIDs valid
* OK [UNSEEN 1] message 1 is first unseen
003 OK [READ-WRITE] SELECT completed

004 FETCH 7214 body[header]
* 7214 FETCH (BODY[header] {639}
Return-Path: <>
X-Antispam-passed: yes
X-Antispam: yes
X-Real-To: user@mail.server.net
Received: from [127.0.0.1] (HELO mail.server.net)
by mail.server.net ( SMTP 4.1.8)
with ESMTP id 22561074 for user@mail.server.net; Sat, 01 Nov 2014 03:21:16 +0300
Received: from localhost.localdomain (localhost [127.0.0.1])
by mail.server.net (Postfix) with SMTP id 1CF5FC0AAE
for <user@mail.server.net>; Sat,  1 Nov 2014 03:20:09 +0300 (MSK)
FROM: root@localhost.localdomain
TO: user@mail.server.net
SUBJECT: test mail from test subject
Message-Id: <20141101002009.1CF5FC0AAE@mail.server.net>
Date: Sat,  1 Nov 2014 03:20:09 +0300 (MSK)

FLAGS (\Seen))
004 OK completed

004 FETCH 7214 body
* 7214 FETCH (BODY ("text" "plain" NIL NIL NIL "8bit" 13 2))
004 OK completed
004 FETCH 7214 body[]
* 7214 FETCH (BODY[] {652}
Return-Path: <>
X-Antispam-passed: yes
X-Antispam: yes
X-Real-To: user@mail.server.net
Received: from [127.0.0.1] (HELO mail.server.net)
by mail.server.net ( SMTP 4.1.8)
with ESMTP id 22561074 for user@mail.server.net; Sat, 01 Nov 2014 03:21:16 +0300
Received: from localhost.localdomain (localhost [127.0.0.1])
by mail.server.net (Postfix) with SMTP id 1CF5FC0AAE
for <user@mail.server.net>; Sat,  1 Nov 2014 03:20:09 +0300 (MSK)
FROM: root@localhost.localdomain
TO: user@mail.server.net
SUBJECT: test mail from test subject
Message-Id: <20141101002009.1CF5FC0AAE@mail.server.net>
Date: Sat,  1 Nov 2014 03:20:09 +0300 (MSK)

test body

)
004 OK completed

005 LOGOUT
* BYE  IMAP closing connection
005 OK completed
Connection closed by foreign host.

POP3
$ telnet 127.0.0.1 110
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
+OK  POP3 Server 4.1.8 ready <137.1414802293@mail.server.net>

USER test@mail.server.net
+OK please send the PASS

PASS testpass
+OK 7214 messages (174404489 bytes)

NOOP
+OK cool

TOP 7214
+OK message follows
Return-Path: <>
X-Antispam-passed: yes
X-Antispam: yes
X-Real-To: test@mail.server.net
Received: from [127.0.0.1] (HELO mail.server.net)
by mail.server.net ( SMTP 4.1.8)
with ESMTP id 22561074 for test@mail.server.net; Sat, 01 Nov 2014 03:21:16 +0300
Received: from localhost.localdomain (localhost [127.0.0.1])
by mail.server.net (Postfix) with SMTP id 1CF5FC0AAE
for <test@mail.server.net>; Sat,  1 Nov 2014 03:20:09 +0300 (MSK)
FROM: root@localhost.localdomain
TO: test@mail.server.net
SUBJECT: test mail from test subject
Message-Id: <20141101002009.1CF5FC0AAE@mail.server.net>
Date: Sat,  1 Nov 2014 03:20:09 +0300 (MSK)

.

RETR 7214
+OK 652 bytes will follow
Return-Path: <>
X-Antispam-passed: yes
X-Antispam: yes
X-Real-To: test@mail.server.net
Received: from [127.0.0.1] (HELO mail.server.net)
by mail.server.net ( SMTP 4.1.8)
with ESMTP id 22561074 for test@mail.server.net; Sat, 01 Nov 2014 03:21:16 +0300
Received: from localhost.localdomain (localhost [127.0.0.1])
by mail.server.net (Postfix) with SMTP id 1CF5FC0AAE
for <test@mail.server.net>; Sat,  1 Nov 2014 03:20:09 +0300 (MSK)
FROM: root@localhost.localdomain
TO: test@mail.server.net
SUBJECT: test mail from test subject
Message-Id: <20141101002009.1CF5FC0AAE@mail.server.net>
Date: Sat,  1 Nov 2014 03:20:09 +0300 (MSK)

test body

.

DELE 7214
+OK marked deleted

QUIT
+OK  POP3 Server connection closed
Connection closed by foreign host.


Существующие способы авторизации: LOGIN, PLAIN, CRAM-MD5, DIGEST-MD5, GSSAPI, NTLM/MSN, EXTERNAL. Перечень их еще шире, мы же рассмотрим только наиболее распространенные, а именно LOGIN, PLAIN и CRAM-MD5.

В первую очередь необходимо узнать список методов, поддерживаемых сервером. Для каждого из почтовых протоколов есть команды, позволяющие получить эти данные наряду с другой информацией о доступных расширениях протокола. Обратите внимание, что в зависимости от настроек почтового сервера, LOGIN и PLAIN, передающие данные в открытом виде, могут быть недоступны без предварительной инициализации шифрования через SSL/TLS

Итак, вывод доступных способов авторизации:

Протокол SMTP


Команда EHLO domainname
$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 mailserver ESMTP ready.

EHLO localhost.localdomain
250-mal.server.net
250-PIPELINING
250-SIZE 104857600
250-ETRN
250-STARTTLS
250-AUTH PLAIN LOGIN DIGEST-MD5 CRAM-MD5
250-AUTH=PLAIN LOGIN DIGEST-MD5 CRAM-MD5
250-ENHANCEDSTATUSCODES
250 8BITMIME
^]
telnet> quit
Connection closed.

Протокол IMAP


Команда 001 CAPABILITY

Какие-то почтовые сервера могут выводить эту информацию в «приветствии сервера», например dovecot.
$ telnet 127.0.0.1 143
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE STARTTLS AUTH=PLAIN AUTH=LOGIN AUTH=DIGEST-MD5 AUTH=CRAM-MD5] Dovecot ready.

001 CAPABILITY
* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT IDLE CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS XEXEC QUOTA STARTTLS AUTH=PLAIN AUTH=LOGIN AUTH=DIGEST-MD5 AUTH=CRAM-MD5
 001 OK Capability completed.

002 LOGOUT
* BYE Logging out
002 OK Logout completed.
Connection closed by foreign host.

Протокол POP3


Команды AUTH или CAPA
$ telnet pop.mail.ru 110
Trying 217.69.139.74...
Connected to pop.mail.ru.
Escape character is '^]'.
+OK

AUTH
+OK methods supported:
LOGIN
PLAIN
.

CAPA
+OK Capability list follows
TOP
USER
LOGIN-DELAY 120
EXPIRE NEVER
UIDL
IMPLEMENTATION Mail.Ru
SASL LOGIN PLAIN
STLS
.

QUIT
+OK POP3 server at  signing off
Connection closed by foreign host.

Примеры авторизации и используемый формат


LOGIN


Протокол SMTP
$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 mail.server.net ESMTP Server

EHLO client.server.net
250-mail.server.net Hello client.server.net
250-AUTH LOGIN PLAIN CRAM-MD5 DIGEST-MD5  GSSAPI
250-ENHANCEDSTATUSCODES
250 STARTTLS

AUTH LOGIN
334 VXNlcm5hbWU6

dGVzdA==
334 UGFzc3dvcmQ6

dGVzdHBhc3M=
235 2.7.0 Authentication successful

QUIT
221 2.0.0 Bye

Где 'dGVzdA== ' — логин и 'dGVzdHBhc3M=' пароль в формате base64. О нем чуть ниже. Обратите внимание, что и логин и пароль должны кодироваться без перевода строки.

PLAIN


Протокол SMTP
$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 mail.server.net ESMTP Server

EHLO client.server.net
250-mail.server.net Hello client.server.net
250-AUTH LOGIN PLAIN CRAM-MD5 DIGEST-MD5  GSSAPI
250-ENHANCEDSTATUSCODES
250 STARTTLS

AUTH PLAIN dGVzdAB0ZXN0AHRlc3RwYXNz
235 2.7.0 Authentication successful

QUIT
221 2.0.0 Bye


Где 'dGVzdAB0ZXN0AHRlc3RwYXNz' это логинпароль в base64 формате. Чуть ниже будут рассмотрены варианты конвертации в base64 формат и обратно.

CRAM-MD5


В отличии от предыдущих способов авторизации CRAM-MD5 пароль не передается в открытом виде, вместо этого используется сравнение хэшей. Ручная проверка этого способа авторизации может быть проблемой, так как нужно будет выполнить несколько преобразований, а время на введение команд ограничено. Для упрощения процесса ниже приведен простой perl скрипт, который принимает на вход имя пользователя, пароль и «кодовое слово» ( выдаваемое сервером ), и конвертирует их в строку в base64 формате.

Для скрипта понадобится дополнительный модуль perl «Digest-HMAC». В Debian/Ubuntu его можно найти и установить следующим образом:

# apt-cache search perl | grep -i digest
# apt-get install libdigest-hmac-perl


Для RHEL/CentOS/Fedora:
# yum search perl | grep -i digest
# yum install perl-Digest-HMAC


В тех дистрибутивах, в чьих репозиториях нет этого пакета(что маловероятно), можно использовать установку модуля из CPAN.

Скрипт и пример сессии с его использованием:

#!/usr/bin/perl -W

use strict;
use MIME::Base64 qw(encode_base64 decode_base64);
use Digest::HMAC_MD5;

die "Usage: $0 username password ticket\n" unless $#ARGV == 2;

my ($username, $password, $ticket64) = @ARGV;

my $ticket = decode_base64($ticket64) or
die ("Unable to decode Base64 encoded string '$ticket64'\n");
my $password_md5 = Digest::HMAC_MD5::hmac_md5_hex($ticket, $password);
print encode_base64 ("$username $password_md5", "");

Протокол SMTP
$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 mail.server.net ESMTP Server

EHLO client.server.net
250-mail.server.net Hello client.server.net
250-AUTH LOGIN PLAIN CRAM-MD5 DIGEST-MD5  GSSAPI
250-ENHANCEDSTATUSCODES
250 STARTTLS

AUTH CRAM-MD5
## кодовое слово, выдаваемое сервером:
PDMzMjE2NDkzMTA1OTExNDQuMTQxNDc5NTExOUBtYWlsLnNlcnZlci5uZXQ+

dGVzdCAxNTU0YTQwNzA1NTgxZjUwZmI1MmNjZDhlZDhjM2EyYg==
235 2.7.0 Authentication successful

QUIT
221 2.0.0 Bye


# ./md5cram.pl test testpass PDMzMjE2NDkzMTA1OTExNDQuMTQxNDc5NTExOUBtYWlsLnNlcnZlci5uZXQ+
dGVzdCAxNTU0YTQwNzA1NTgxZjUwZmI1MmNjZDhlZDhjM2EyYg==

Протокол IMAP
$ telnet 127.0.0.1 143
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE STARTTLS AUTH=PLAIN AUTH=LOGIN AUTH=DIGEST-MD5 AUTH=CRAM-MD5] Dovecot ready.

01 AUTHENTICATE  CRAM-MD5
+ PDgxOTAyMjA2NTYwNzcyMzEuMTQxNDc5NzA3MkBtYWlsLnNlcnZlci5uZXQ+

dGVzdCA1YTZlNjYwMDlmZGJlZWNjYWRlNDY5M2FlMjU5YTA2ZQ==
01 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS MULTIAPPEND UNSELECT IDLE CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS XEXEC QUOTA] Logged in

02 LOGOUT
* BYE Logging out
02 OK Logout completed.
Connection closed by foreign host.

# ./md5cram.pl test testpass PDgxOTAyMjA2NTYwNzcyMzEuMTQxNDc5NzA3MkBtYWlsLnNlcnZlci5uZXQ+
dGVzdCA1YTZlNjYwMDlmZGJlZWNjYWRlNDY5M2FlMjU5YTA2ZQ==

Cпособы конвертации текста в и из base64


Авторизация предполагает обмен строками, закодированными в base64. Для Linux cуществует много утилит для конвертации в base64 и обратно. Мы укажем несколько, включая способ их запуска. Для Windows можно использовать кроссплатформенные perl, python, php, по ним также будут привидены примеры.

Утилита (пакет)


base64 (coreutils)

$ printf 'test\0test\0testpass' | base64
dGVzdAB0ZXN0AHRlc3RwYXNz

$ echo dGVzdAB0ZXN0AHRlc3RwYXNz | base64 -d
testtesttestpass


uueencode/uudecode (sharutils)

$ printf 'test\0test\0testpass' | uuencode -m -
begin-base64 644 -
dGVzdAB0ZXN0AHRlc3RwYXNz
====


Чтобы раскодировать, потребуется добавить первую и последнюю строку. Это можно сделать, например, следующими способами;
printf 'begin-base64 644 -\ndGVzdAB0ZXN0AHRlc3RwYXNz\n====' | uudecode


или
$ uudecode<<EOF
begin-base64 644 -
dGVzdAB0ZXN0AHRlc3RwYXNz
====
EOF


mmencode (xemacs21-bin)

$ printf 'test\0test\0testpass' | mmencode
dGVzdAB0ZXN0AHRlc3RwYXNz

$ echo dGVzdAB0ZXN0AHRlc3RwYXNz | mmencode -u
testtesttestpass


python (python)

$ printf 'test\0test\0testpass' |  python -m base64
dGVzdAB0ZXN0AHRlc3RwYXNz
$ echo dGVzdAB0ZXN0AHRlc3RwYXNz | python -m base64 -d


php (php-cli)

$ printf 'test\0test\0testpass' | php -r 'echo base64_encode(fgets(STDIN));'
dGVzdAB0ZXN0AHRlc3RwYXNz
$ php -r 'echo base64_decode($argv[1]);' dGVzdAB0ZXN0AHRlc3RwYXNz
testtesttestpass


perl (perl)


Модуль MMIME::Base64 стандартно идет в комплекте.
$ perl -MMIME::Base64 -e 'print encode_base64("test\0test\0testpass")'
dGVzdAB0ZXN0AHRlc3RwYXNz
$ perl -MMIME::Base64 -e 'print decode_base64("dGVzdAB0ZXN0AHRlc3RwYXNz")'
testtesttestpass

openssl (openssl)

$ printf 'test\0test\0testpass' |  openssl base64
dGVzdAB0ZXN0AHRlc3RwYXNz
$ echo dGVzdAB0ZXN0AHRlc3RwYXNz | openssl base64 -d
testtesttestpass


Для шифрования трафика в почтовых протоколах между клиентом и сервером используется SSL/TLS в двух вариантах. Использование специальных портов, при соединении с которым сначала осуществляется установка SSL/TLS, после чего уже поверх него идет обычный почтовый трафик. Этот метод, кстати, признан устаревшим (deprecated), относительно SMTP точно. Второй вариант, более предпочтительный — соединение с обычным портом для сервиса и переход сессии в зашифрованный вид с использованием расширения STARTTLS.

Для проверки работы почтового сервера поверх SSL/TLS можно использовать утилиту openssl, дальше действуя, как при обычной сессии через telnet.

SMTP

$ openssl s_client -starttls smtp -crlf -connect mail.truevds.ru:25
$ openssl s_client -starttls smtp -crlf -connect mail.truevds.ru:587
$ openssl s_client -crlf -connect mail.truevds.ru:465

POP3

$ openssl s_client -connect mail.truevds.ru:995
$ openssl s_client -starttls pop3 -crlf -connect mail.truevds.ru:110

IMAP

$ openssl s_client -crlf -connect mail.truevds.ru:993
$ openssl s_client -starttls imap -crlf -connect mail.truevds.ru:143

Можно явным образом указать, что использовать для шифрования, ssl3 или tls1, а также конкретные алгоритмы:

$ openssl s_client -ssl3 -starttls smtp -crlf -connect mail.truevds.ru:25

Посмотреть перечень поддерживаемых протоколов в вашей версии openssl:

$ openssl ciphers -ssl3
$ openssl ciphers -tls1

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


При необходимости более сложной диагностики в том случае, когда журналы не дают достаточно информации о проблемах в работе сервера или клиента, можно использовать tcpdump/wireshark для анализа непосредственно самой сессии между клиентом и сервером. Как в реальном времени, так и сохранив дамп сессии для последующего анализа. Для быстрого анализа удобно использовать консольный вариант wireshark — tshark. Для его работы потребуются права root.

Tshark предоставляет информацию в понятном виде и в использовании довольно прост.

SMTP

# tshark -i eth0 -f "port 25" -R smtp

IMAP

# tshark -i eth0 -f "port 143" -R imap

POP3

# tshark -i eth0 -f "port 110" -R pop

Запись трафика для последующего анализа при помощи утилит tcpdump|dumpcap(из состава wireshark):

# tcpdump -s0 -nn -i eth0 -w smtps.pcap port 465 and host HOSTIP
# dumpcap -s0 -i eth0 -w smtp.pcap -f 'port 25 and host HOSTIP'

где HOSTIP является IP-адресом противоположной стороны, сервера или клиента, сессию с которым мы анализируем. И последующее чтение:

# tshark -n -r smtp.pcap -R smtp

Во многих случаях в почтовых протоколах активно используется шифрование и таким способом сессию уже не посмотреть. Тем не менее, этот вопрос в целом также решаем. tshark может дешифровать SSL/TLS трафик «со стороны сервера» при наличии доступа к приватному ключу сервера.(Для клиента есть вариант с использованием Master-Key, подробнее http://ift.tt/AbMy0g) К счастью или к сожалению, wireshark с приватным ключем может дешифровать не все использвуемые алгоритмы. Например DHE-* EXP-*,EDH-* не работают. Возможно, какие-то из этих алгоритмов добавлены в более поздних версиях программы.

В процессе тестирования использовалась утилита openssl с явным указанием при соединении с конкретных алгоритмов. Проверенные варианты, с которыми дешифровка трафика прошла успешно:

  • ssl3: RC4-SHA, RC4-MD5, DES-CBC-SHA, AES128-SHA
  • tls1: RC4-MD5, AES256-SHA, DES-CBC-SHA, DES-CBC3-SHA

Посмотреть перечень поддерживаемых протоколов в вашей версии openssl:

# openssl ciphers -ssl3
# openssl ciphers -tls1

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

Tshark запускается на сервере, там, где есть ключ, а клиент openssl на локальном компьютере. Но, это, конечно, необязательно, вполне можно tshark запускать на клиенте в другой консоли, просто это потребует копирования приватного ключа на локальный компьютер. А openssl можно запускать в screen в соседнем с tshark окне.

Итак запускаем:

# tshark -i eth0 -n -o "ssl.keys_list:94.127.66.53,25,smtp,/etc/pki/tls/private/server.key" -R smtp
$ printf "EHLO RC4-MD5\nEXIT" | openssl s_client -starttls smtp -crlf -tls1 -cipher RC4-MD5 -connect mail.truevds.ru:25

# tshark -i eth0 -n -o "ssl.keys_list:94.127.66.53,465,smtp,/etc/pki/tls/private/server.key" -R smtp
$ printf "EHLO RC4-MD5\nEXIT"  | openssl s_client -ssl3 -cipher RC4-SHA -connect mail.truevds.ru:465

# tshark -i eth0 -n -o "ssl.keys_list:94.127.66.53,143,imap,/etc/pki/tls/private/server.key" -R imap
$ printf "* CAPABILITY\nLOGOUT" | openssl s_client -starttls imap -crlf -tls1 -cipher RC4-MD5 -connect mail.truevds.ru:143

# tshark -i eth0 -n -o "ssl.keys_list:94.127.66.53,993,imap,/etc/pki/tls/private/server.key" -R imap
$ printf "* CAPABILITY\nLOGOUT"  | openssl s_client -crlf -ssl3 -cipher RC4-MD5 -connect mail.truevds.ru:993

# tshark -i eth0 -n -o "ssl.keys_list:94.127.66.53,110,pop,/etc/pki/tls/private/server.key" -R pop
$ printf "USER RC4-MD5\nEXIT" | openssl s_client -starttls pop -crlf -tls1 -cipher RC4-MD5 -connect mail.truevds.ru:110

# tshark -i eth0 -n -o "ssl.keys_list:94.127.66.53,995,pop,/etc/pki/tls/private/server.key" -R pop
$ printf "USER RC4-MD5\nEXIT" |  openssl s_client -crlf -ssl3 -cipher RC4-MD5 -connect mail.truevds.ru:995

Здесь 94.127.66.53 — ip адрес сервера, с которым соединяется клиент, /etc/pki/tls/private/server.key — путь до приватного ключа сервера. Приватный ключ, как правило, размещается в /etc/pki или /etc/ssl, в зависимости от сервера. Эту информацию можно посмотреть в настройках самого почтового сервера.

Пример для postfix:

$ grep key_file /etc/postfix/main.cf
smtpd_tls_key_file = /etc/pki/tls/private/server.key
smtp_tls_key_file = /etc/pki/tls/private/server.key

Для портов, где используется starttls вместо порта в официальной документации рекомендуется использовать start_tls. Например, ssl.keys_list:94.127.66.53,start_tls,smtp,/etc/pki/tls/private/server.key вместо ssl.keys_list:94.127.66.53,25,smtp,/etc/pki/tls/private/server.key. Но у меня этот вариант не сработал, показывался трафик только до инициализации шифрования.

Для отладки процесса SSL/TLS дешифровки используется опция -o "ssl.debug_file: /tmp/debug.log"

Пример вывода дешифрованного трафика:

# tshark -i eth0 -n -o "ssl.keys_list:94.127.66.53,25,smtp,/etc/pki/tls/private/server.key" -R "smtp" 
Running as user "root" and group "root". This could be dangerous.
Capturing on eth0
0.178964 94.127.66.21 -> 94.127.66.53 SMTP C: EHLO RC4-MD5 | EXIT
0.179357 94.127.66.53 -> 94.127.66.21 SMTP 250-mail.truevds.ru | 250-PIPELINING | 250-SIZE 104857600 | 250-ETRN | 


Удачи в решении почтовых проблем!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Сервис распознавание котов

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

Да и вообще, можно ли создать сервис, уверенно распознающий котов с учётом присущего последним стремления принять самую неожиданную позу? Давайте попробуем.


Для решения поставленной задачи была выбрана PaaS-платформа IBM Bluemix, которая объединяет около сотни различных сервисов на все случаи жизни. Там есть отдельная группа Watson, содержащая различные инструменты для решения задач когнитивных вычислений – очень похоже на то, что нам нужно.

Среди сервисов Watson обнаружилось нечто под названием AlchemyAPI, что, в свою очередь, выглядит как универсальный набор когнитивного алхимика, представленный в виде унифицированных REST API. И там есть API для Image Tagging. API очень простой. Просто передаём ему изображение либо в виде файла, либо в виде URL, а он возвращает JSON c набором тегов и коэффициентов релевантности. То есть всё, что нужно будет сделать, это написать небольшой сервис, который будет получать файл с картинкой, отправлять его AlchemyAPI и говорить нам, что в результате получилось.

Разработка была на питоне, в качестве HTTP-сервера взяли Tornado. В принципе, можно взять любой другой, просто к Tornado я привык и на нём все получается просто и быстро.

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

Начнем с того, что создадим под нашим аккаунтом на Bluemix рабочую среду на питоне. Если у вас ещё нет аккаунта, зарегистрируйтесь – это быстро, бесплатно и без СМС. Идём в раздел Dashboard и нажимаем Create App, затем выбираем тип приложения Web и получаем список доступных сред для работы нашего сервиса (нам нужен Python). Добавляем сервису имя (я назвал его catreco) и где-то через полминуты вы увидите, что среда создана, и там находится Hello World посмотреть на который можно по ссылке http://ift.tt/1LqyQdG.

Теперь пора заняться делом. Bluemix предлагает два способа сихронизации рабочей среды между локальным компьютером и облаком: интерфейс командной строки CF и привычный большинству из нас GIT, который мы и выберем. В меню слева нажимаем на  Overview и попадаем на страницу нашего приложения. Нажимаем кнопку Add GIT и у нас появляется GIT URL, указывающий на только что созданный репозиторий, содержащий Hello World.

Сам по себе Hello World мало интересен, однако, репозиторий содержит несколько полезных файлов, необходимых для развертывания нашего приложения в облаке.

Склонируем этот репозиторий на наш компьютер:

git clone <адрес репозитория>

Нам понадобятся следующие файлы (остальное можно удалить):
manifest.yml,
Procfile,
requirements.txt.

Теперь дело за нашим сервисом-распознавателем. Его код я выложил сюда. Загрузите его на свой компьютер и скопируйте содержимое в папку catreco. В самом коде нет ничего заслуживающего особого внимания – лишь несколько десятков строк, поэтому я не буду его подробно комментировать. Хотя и есть несколько нюансов.

Во-первых, нужно подключить сервис AlchemyAPI, для чего возвращаемся на Bluemix в раздел Dashboard и нажимаем кнопку Use Services or APIs, а затем выбираем сервис AlchemyAPI. На странице конфигурации сервиса указано, что нужно создать аккаунт на самом AlchemyAPI – заходим по ссылке, создаём аккаунт и получаем на почту ключ. Этот ключ нужно скопировать в переменную APIKEY в файле webserver.py. На этом конфигурацию сервиса можно считать законченной.

Во-вторых, при запуске HTTP-сервера в своей рабочей среде Bluemix использует специальный порт, отличный от 80-го и уже затем, с помощью внутренних механизмов маршрутизации, позволяет вам обращаться к вашему приложению по 80 порту. Нужно это предусмотреть в коде сервера webserver.py:

PORT = int(os.getenv('VCAP_APP_PORT', 8080))

…

application.listen(PORT)
 
То есть если мы находимся в среде Bluemix, то сервер будет запущен по порту, содержащемуся в переменной 'VCAP_APP_PORT', а с локальной машины по порту 8080 или тому, который будет удобен. В большинстве случаев, это единственная специфическая настройка, которую вам нужно предусмотреть при переносе вашего кода в среду Bluemix.

В третьих, нужно подготовить среду на Bluemix к запуску нашего приложения. В данный момент там есть только python 2.7.8 и стандартные библиотеки, а нам нужно ещё установить Tornado и модуль requests, объяснить Bluemix какой файл должен быть запущен. За это и отвечают файлы, которые мы забрали из Hello World.

Файл manifest.yml содержит конфигурацию нашей рабочей среды. Здесь мы ничего менять не будем:

applications:
- disk_quota: 1024M
  host: catreco
  name: catreco
  path: .
  domain: mybluemix.net
  instances: 1
  memory: 128M

Файл Procfile содержит информацию о том, какой командой запускать наше приложение. Наш исполняемый файл называется webserver.py, поэтому пишем:
web: python webserver.py

Файл requirements.txt содержит список модулей, которые должны быть установлены в рабочей среде. Увидев этот список, Bluemix запустит pip и установит всё необходимое:
tornado>=4.0
requests>=2.7

На этом подготовка закончена. Мы сконфигурировали внешний сервис AlchemyAPI, учли особенности среды Bluemix при запуске HTTP-сервера и задали необходимые параметры для развёртывания нашего приложения в облаке.

Попробовать запустить приложение можно на локальной машине, для чего в командной строке пишем python webserver.py и набираем в браузере заветный localhost:8080

То есть то, что лежит у меня на коленях, является котом с вероятностью 91%. Я пробовал другие ракурсы, брал картинки из сети – работает! Так что дело за малым – перенестись в облака. В папке, куда мы сначала склонировали Hello World, а затем разместили код нашего проекта, уже есть всё необходимое – дальще просто обновляем локальный репозиторий:

git add .
git commit –m "first deployment"

И отправляем его обратно на Bluemix:
git push

Bluemix проделает все необходимые операции по сборке нового приложения (вместо Hello World теперь будет наш код) и запустит его. Как только статус нашего приложения станет Your app is running, можно пойти по ссылке  http://ift.tt/1LqyQdG и убедиться, что наш сервис работает в облаке, его можно попробовать с любого устройства. Красивый плагин для загрузки файлов взят отсюда.

Примеры работы:

Cat — 94%

Cat — 48%, pet — 63%

Cat — 89%, rabbit — 80%

Cat — 99%, kitten — 50%

Cat — 71%, dog — 59%

Предыдущая картинка похожа на тигра, при этом: tiger — 99%, cat — 75%

Dog — 99%, puppy — 68%

Dog — 99%

Cat — 99%, dog — 57%

Cat: 99%

Cat — 65%, eagle — 43%

Если вы где-то ошиблись и что-то пошло не так – don’t panic. Над статусом вашего приложения на панели Bluemix есть волшебная кнопка Edit Code, нажатие которой перенесёт вас в сервис DevOps, где есть редактор кода, средства управления репозиторием и построением билдов, а также доступ ко всем логам. Сервис большой и его описание займёт больше времени, чем весь наш проект – если и потребуется, то обратитесь к документации.

Вместо вывода:

  1. Мы научились распознавать котов с помощью простого веб-приложения и сервиса, предоставляемого платформой Bluemix;
  2. Мы теперь знаем, как подготовить приложение для размещения в облаке Bluemix и как, собственно, разместить его с помощью одной команды git;
  3. Мы знаем куда смотреть если что-то пошло не так;
  4. Как вы уже поняли, дело может не ограничиваться только лишь котами.

По-моему, неплохо для одного дождливого утра? Расчехляйте папки с котиками, пробуйте. Котики, мотайте на ус, как теперь лучше маскироваться.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Интеграция Unity с Google Таблицами

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

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

В этой статье я хочу рассказать Вам какую пользу принесли мне электронные таблицы, с какими трудностями я столкнулся, когда подготавливал игру для работы с Google таблицами и также хочу поделиться небольшим туториалом по интеграции Unity проекта с Google Spreadsheets.

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

Как вы может заметили разные цветовые схемы добавили в довольно простую игру изюминку, сделали ее чуть привлекательнее и интереснее для пользователя. Без систематизации данных это сделать было бы практически невозможно.

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

А сейчас перейдем к техническому аспекту интеграции Google таблиц с Unity. Игровой движок Unity предоставляет множество способов взаимодействия с интернет ресурсами, в том числе и с таблицами Google, одним из них является интеграция на базе standalone-приложения. Но основным вызовом в этом случае может быть использование движком .NET 2.0.

С 5 мая 2015 года Google прекратили поддержку устаревшего протокола авторизации OAuth 1.0, и все те приложения, которые не перешли на новый OAuth 2.0 перестали работать. Так вышло что разработчики бесплатных плагинов, которым я пользовался, Unity-GData и его обновленной версии Unity-Quicksheet об этом не позаботились, и мне ничего не оставалось как искать решение самому.

Поначалу мне казалось, что проблем с авторизацией на Unity и С# возникнуть не должно, потому что документация на эту тему исчерпывающая. Но оказалось не все так просто.

Во-первых, были заминки с самим процессом авторизации, так как некоторые параметры для запроса OAuth 2.0 в документации к Google Spreadsheets не были указаны, например “Access Type” и “Token Type”.

Во-вторых, в некоторых обновленных библеотеках .dll Google Data API, которые я импортировал из Google Data API SDK для mono, в Unity возникали ошибки при компиляции, тоже самое было и с рядом старых .dll из плагина Unity-GData. Пришлось комбинировать.

В-третьих нужно было еще добавить подходящую библиотеку Newtonsoft для работы с JSON запросами.

Перейдем к делу. Процесс интеграции Google таблиц с Unity можно разделить на несколько этапов.

  • Предварительная настройка аккаунта Google
  • Настройка доступа к Google Drive API
  • Получение и сохранение данных из Google таблиц

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

Предварительная настройка аккаунта Google

  • Входим в свой аккаунт.
  • Переходим по ссылке и создаем новый проект “Create new project”.
  • На боковой панели консоли разработчика в разделе “APIs & Auth” выбираем вкладку “APIs”.
  • Переходим в настройки “Drive API” в группе “Google Apps Api” и подключаем данный API к нашему приложению кнопкой “Enable API”.
  • В том же разделе “APIs & Auth” выбираем вкладку “Consent screen”. Вводим имя продукта “Product name” и по желанию указываем адрес домашней страницы, логотип и другие параметры.
  • Снова разделе “APIs & Auth” выбираем вкладку “Credentials”. Создаем новый Client ID для работы с Auth 2.0 — кнопка “Create client ID”. В диалоговом окне отмечаем “Application type” как “Installed application”, а “Installed application type”, как “Other”. Подтверждаем нажатием кнопки “Create Client ID”.
  • Мы получили значения Client ID, Client Secret, которые потребуются в дальнейшем.

Настройка доступа к Google Drive API

  • После того как вы импортировали unitypackage, скрипт “SpreadsheetEntity.cs”, должен автоматически начать свою работу при запуске Unity. Открываем скрипт для редактирования, и вводим информацию для авторизации.
  • Присваиваем переменным _ClientId и _ClientId значения Client ID и Client Secret полученные из консоли разработчика.
  • Запускаем Unity проект. Он автоматически переходит по ссылке, где после авторизации, можно получить Access Code для того, чтобы приложение могло успешно авторизоваться с Google.
  • Копируем Access Code и присваиваем его значение переменной _AccessCode в скрипте “SpreadsheetEntity.cs”.
  • После этого снова запускаем проект и получаем из лога консоли Unity значения OAuth 2.0 Access Token и Refresh Token, которые нужны нам для постоянного доступа к таблицам Google. Присваиваем эти значения переменным _AccessToken и _RefreshToken соответственно.

Приведу пример скрипта.
Тут находиться скрипт
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using Google.GData.Client;
using Google.GData.Spreadsheets;

[InitializeOnLoad]
public class SpreadsheetEntity : MonoBehaviour 
{
        // enter Client ID and Client Secret values
        const string _ClientId = "";
        const string _ClientSecret = "";
        // enter Access Code after getting it from auth url
        const string _AccessCode = "";
        // enter Auth 2.0 Refresh Token and AccessToken after succesfully authorizing with Access Code
        const string _RefreshToken = "";
        const string _AccessToken = "";

        const string _SpreadsheetName = "";


        static SpreadsheetsService service;

        
        public static GOAuth2RequestFactory RefreshAuthenticate() 
        {
                OAuth2Parameters parameters = new OAuth2Parameters()
                {
                        RefreshToken = _RefreshToken,
                        AccessToken = _AccessToken,
                        ClientId = _ClientId,
                        ClientSecret = _ClientSecret,
                        Scope = "http://ift.tt/JALASw http://ift.tt/1cMkUd0",
                        AccessType = "offline",
                        TokenType = "refresh"
                };
                string authUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
                return new GOAuth2RequestFactory("spreadsheet", "MySpreadsheetIntegration-v1", parameters);
        }

        static void Auth()
        {
                GOAuth2RequestFactory requestFactory = RefreshAuthenticate();
                
                service = new SpreadsheetsService("MySpreadsheetIntegration-v1");  
                service.RequestFactory = requestFactory;
        }
        

        // Use this for initialization
        static SpreadsheetEntity(){
                if (_RefreshToken == "" && _AccessToken == "")
                {
                        Init();
                        return;
                }
                
                Auth();
                
                Google.GData.Spreadsheets.SpreadsheetQuery query = new Google.GData.Spreadsheets.SpreadsheetQuery();
                
                // Make a request to the API and get all spreadsheets.
                SpreadsheetFeed feed = service.Query(query);
                
                if (feed.Entries.Count == 0)
                {
                        Debug.Log("There are no spreadsheets in your docs.");
                        return;
                }
                
                AccessSpreadsheet(feed);
        }

        // access spreadsheet data
        static void AccessSpreadsheet(SpreadsheetFeed feed)
        {

                string name = _SpreadsheetName;
                SpreadsheetEntry spreadsheet = null;

                foreach (AtomEntry sf in feed.Entries)
                {
                        if (sf.Title.Text == name)
                        {
                                spreadsheet = (SpreadsheetEntry)sf;
                        }
                }

                if (spreadsheet == null)
                {
                        Debug.Log("There is no such spreadsheet with such title in your docs.");
                        return;
                }

                
                // Get the first worksheet of the first spreadsheet.
                WorksheetFeed wsFeed = spreadsheet.Worksheets;
                WorksheetEntry worksheet = (WorksheetEntry)wsFeed.Entries[0];
                
                // Define the URL to request the list feed of the worksheet.
                AtomLink listFeedLink = worksheet.Links.FindService(GDataSpreadsheetsNameTable.ListRel, null);
                
                // Fetch the list feed of the worksheet.
                ListQuery listQuery = new ListQuery(listFeedLink.HRef.ToString());
                ListFeed listFeed = service.Query(listQuery);


                foreach (ListEntry row in listFeed.Entries)
                {
                        //access spreadsheet data here
                }


        }
        
        static void Init()
        {
                
                ////////////////////////////////////////////////////////////////////////////
                // STEP 1: Configure how to perform OAuth 2.0
                ////////////////////////////////////////////////////////////////////////////

                if (_ClientId == "" && _ClientSecret == "")
                {
                        Debug.Log("Please paste Client ID and Client Secret");
                        return;
                }

                string CLIENT_ID = _ClientId;

                string CLIENT_SECRET = _ClientSecret;

                string SCOPE = "http://ift.tt/JALASw http://ift.tt/1cMkUd0 http://ift.tt/1lDQf2m";

                string REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
                
                string TOKEN_TYPE = "refresh";
                
                ////////////////////////////////////////////////////////////////////////////
                // STEP 2: Set up the OAuth 2.0 object
                ////////////////////////////////////////////////////////////////////////////
                
                // OAuth2Parameters holds all the parameters related to OAuth 2.0.
                OAuth2Parameters parameters = new OAuth2Parameters();

                parameters.ClientId = CLIENT_ID;

                parameters.ClientSecret = CLIENT_SECRET;

                parameters.RedirectUri = REDIRECT_URI;
                
                ////////////////////////////////////////////////////////////////////////////
                // STEP 3: Get the Authorization URL
                ////////////////////////////////////////////////////////////////////////////

                parameters.Scope = SCOPE;
                
                parameters.AccessType = "offline"; // IMPORTANT and was missing in the original
                
                parameters.TokenType = TOKEN_TYPE; // IMPORTANT and was missing in the original

                // Authorization url.

                string authorizationUrl = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
                Debug.Log(authorizationUrl);
                Debug.Log("Please visit the URL above to authorize your OAuth "
                          + "request token.  Once that is complete, type in your access code to "
                          + "continue...");

                parameters.AccessCode = _AccessCode;

                if (parameters.AccessCode == "")
                {
                        Application.OpenURL(authorizationUrl);
                        return;
                }
                ////////////////////////////////////////////////////////////////////////////
                // STEP 4: Get the Access Token
                ////////////////////////////////////////////////////////////////////////////

                OAuthUtil.GetAccessToken(parameters);
                string accessToken = parameters.AccessToken;
                string refreshToken = parameters.RefreshToken;
                Debug.Log("OAuth Access Token: " + accessToken + "\n");
                Debug.Log("OAuth Refresh Token: " + refreshToken + "\n");
        
        }
        
}



Отмечу некоторую особенность: этот скрипт выполняется при запуске редактора Unity, это очень удобно, потому что все данные уже будут на месте перед выполнением основного кода приложения, подробнее здесь.

После того как вы выполнили все необходимые шаги, проект готов для работы с Google таблицами.

Получение и хранение данных с Google таблиц

У Google процесс работы с таблицами хорошо описан в выше упомянутой документации для разработчиков. Но для ясности приведу небольшой пример. Для получения данных, я использую запросы на основе списков (list-based feeds), а для хранения — файлы XML. Более подробную информацию по работе с XML в Unity вы можете найти здесь.

Пример кода
// modified AccessSpreadsheet Method
void AccessSpreadsheet(SpreadsheetFeed feed)
        {
 
                string name = _SpreadsheetName;
                SpreadsheetEntry spreadsheet = null;
 
                foreach (AtomEntry sf in feed.Entries)
                {
                        if (sf.Title.Text == name)
                        {
                                spreadsheet = (SpreadsheetEntry)sf;
                        }
                }
 
                if (spreadsheet == null)
                {
                        Debug.Log("There is no such spreadsheet with such title in your docs.");
                        return;
                }
 
               
                // Get the first worksheet of the first spreadsheet.
                WorksheetFeed wsFeed = spreadsheet.Worksheets;
                WorksheetEntry worksheet = (WorksheetEntry)wsFeed.Entries[0];
               
                // Define the URL to request the list feed of the worksheet.
                AtomLink listFeedLink = worksheet.Links.FindService(GDataSpreadsheetsNameTable.ListRel, null);
               
                // Fetch the list feed of the worksheet.
                ListQuery listQuery = new ListQuery(listFeedLink.HRef.ToString());
                ListFeed listFeed = service.Query(listQuery);
               
                //create list to add dynamic data
                List<TestEntity> testEntities = new List<TestEntity>();
 
                foreach (ListEntry row in listFeed.Entries)
                {
                        TestEntity entity = new TestEntity();
                        entity.name = row.Elements[0].Value;
                        entity.number = int.Parse(row.Elements[1].Value); //use Parse method to get int value
                        Debug.Log("Element: " + entity.name + ", " + entity.number.ToString());
                        testEntities.Add(entity);
                }
 
                TestContainer container = new TestContainer(testEntities.ToArray());
 
                container.Save("test.xml");
 
        }
 
// classes for xml serialization
public class TestEntity {
 
        public string name;
        public int number;
       
        public TestEntity(){
                name = "default";
                number = 0;
        }
}
 
[XmlRoot("TestCollection")]
public class TestContainer {
        [XmlArray("TestEntities")]
        [XmlArrayItem("testEntity")]
        public TestEntity[] testEntities;// = new skinEntity[];
       
        public TestContainer(){
        }
       
        public TestContainer(TestEntity[] arch){
                testEntities = arch;
        }
       
        public void Save(string path)
        {
                var serializer = new XmlSerializer(typeof(TestContainer));
                using(var stream = new FileStream(path, FileMode.Create))
                {
                        serializer.Serialize(stream, this);
                }
        }
       
        public static TestContainer Load(string path)
        {
                var serializer = new XmlSerializer(typeof(TestContainer));
                using(var stream = new FileStream(path, FileMode.Open))
                {
                        return serializer.Deserialize(stream) as TestContainer;
                }
        }
}



Стоит еще добавить, что скрипт работает только в редакторе, использовать функционал Google таблиц на устройстве нельзя, по крайней с теми версиями плагинов, которые я нашел, это связано с проблемами совместимости части библиотек с некоторыми платформами. Предложенные мной библиотеки в unitypackage не будут компилироваться ни для одной из платформы, кроме редактора. Если Вам все-таки потребуется использовать таблицы на устройстве, то в 5 версии Unity можно выбрать платформу, которую должен поддерживает плагин, с помощью Plugin Inspector, а в более ранних версиях — поместить плагин в нужную папку, подробнее здесь.

Итак, электронные таблицы являются незаменимым инструментов в процессе разработки игр. Они помогают оптимизировать рутинную работу и наладить взаимодействие всей команды, визуализировать огромные массивы данных и использовать математику для их расчета. Интеграция Google таблиц с Unity — процесс несложный, но он требует немного знаний и усилий

Вот ссылка на GitHub проекта.

Спасибо за внимание, надеюсь статья была Вам полезна.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.