среда, 26 августа 2015 г.

Slackware: iptables и правила со множеством сетей

Возникла тут одна небольшая проблема. Пусть есть у нас некий линуксовый сервер, который служит шлюзом между тремя сетями, и в котором в iptables прописано много забавных маршрутов. Две сети - локальные, скажем, 10.0.0.0/8 и 192.168.0.0/16, воткнутые каждая в свою сетевую карточку, и третья - Интернет за NAT c внешним адресом X.X.X.X. И нужно какому-нибудь конкретному локальному адресу, например, 10.Y.Y.Y, дать доступ к ресурсам обеих локальных сетей и в то же время пустить в Интернет.

Было бы прекрасно, если бы можно было в параметрах iptables перечислить несколько подсетей, например, так:
iptables -A POSTROUTING -t nat -s 10.Y.Y.Y -d !10.0.0.0/8,!192.168.0.0/16 --to-source X.X.X.X
но, к сожалению, этот вариант у меня не сработал: iptables не позволил указать несколько подсетей таким образом и заругался на синтаксис.

Насколько я понял, в этом случае решением является следующее: создать отдельную цепочку правил, в которой последовательно проверить назначения пакета:
# создаем цепочку правил
iptables -N MYCHAIN -t nat
iptables -A MYCHAIN -t nat -d 10.0.0.0/8     -j RETURN
iptables -A MYCHAIN -t nat -d 192.168.0.0/16 -j RETURN
iptables -A MYCHAIN -t nat                   -j SNAT --to-source X.X.X.X

# используем цепочку правил
iptables -A POSTROUTING -t nat -s 10.Y.Y.Y -j MYCHAIN

четверг, 6 августа 2015 г.

WinXP + IE8 + Javascript

Если в браузере Internet Explorer 8 под WindowsXP не работает Javascript (от слова "вообще"), то иногда помогает команда:
regsvr32 c:\windows\system32\jscript.dll
А если не работает VBScript, то команда:
regsvr32 c:\windows\system32\vbscript.dll

среда, 3 июня 2015 г.

чтобы не потерять

1.
В белом небе растает стремительный след,
Будет спуск протекать по спиралям крутым,
Я вернусь постаревшим на тысячу лет
и таким же, как прежде -
почти молодым.

Знаю, век - это миг,
Знаю, век - это час,
Значит, тот, кто давно,
Это всё же - сейчас.
Значит, я, а не он - обезьяний стан
Выпрямлял, не умея плавить руду,
Сбил на землю камнем спелый банан,
И потомки сказали:
- Слава труду! -

А потом в белом небе - стремительный след,
И виски от космической пыли как дым...
Это мир постареет на тысячу лет,
Это я остаюсь навсегда молодым.

2.
Земля зелёным мячиком
Мчится сквозь года.
О чём же вы, мальчики,
Спорите тогда?
Мы, Прометеи,
Создатели огня!
Есть идея -
Нет коня.
Время забыто
Счёта нет.
Бывают же орбиты
Без планет.
Расщеплён стронций.
Умчались ввысь.
Но кто же крикнет Солнцу:
- Эй, остановись!
Не хочу в старость,
Лучше - в никуда.

Пой, гитара,
Пока молода.
Беспощадность клеток -
Спасенья нет.
Сегодня малолеток.
Завтра - дед.
Хочу в невесомость.
Возмите нас...
Сейчас - потомок,
Предок - через час.
Земля зелёным мячиком
Лупит сквозь ночь.
Мальчики, мальчики,
Чем же вам помочь?

3.
Ожидала звонка.
Ожидала давно.
За погашенной шторой светилось окно.
Он вошёл и сказал, бросив шляпу на стол:
- Добрый день, дорогая. Я прощаться пришёл... -
Что ж, прощаются люди на день
и на два,
Иногда на года.
Он сказал:
- Я прощаться пришёл навсегда...

Да, об этом узнала она из газет:
В белом небе растает стремительный след.
И на тысячу лет...
И на тысячу лет.

- Я хочу попрощаться с тобой навсегда.
Ты меня понимаешь? -
Ответила: - Да. -
Человеческий век бесконечно жесток,
Человеческий век - электрический ток.
Только бронза способна века простоять.
Но ответила: - Да. Я тебя буду ждать. -
Буду ждать...

Человек превратится в мечту.
Буду ждать...
Но кого? Разве ждут пустоту?
Разве можно дождаться того, кто "никто",
Кто оставит всего лишь стремительный след?!
Человек не способен жить тысячу лет.
Все уйдут - мудрецы и герои труда,
- Ты меня понимаешь? -
Ответила: - Да. Я люблю...
Я люблю навсегда.

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

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

Мы с собою берём в дорогу
Веру чистую, как кристалл,
Ту, которой жрецу, как Богу,
Первый верующий присягал.

Ярославны, вы вслед не плачьте нам,
У мужчин есть такая слабость:
Убежать за сто тысяч вёрст,
Хватануть не горя, так славы,
Окунуться в пламя звёзд,
Неизмеренное - измерить,
Неизведанное - пройти...
Ярославны, ждите, верьте,
Мы - в пути.

5.
Война и мир,
Добро и зло.
О, человечество, куда тебя несло?
Была вода.
Придумали огонь.
Но славы ради ли ради женщин
Бумагу исчертил Ньютон?
Прокатный стан родил кусочек жести...
Добро и зло!
Кто, прошлое найдя,
Осудит беспристрастней, чем судья?
... В Браунау жил крепкоголовый мальчик.
О человечество, боготвори вождя!
И он проколет Землю, словно мячик.

6.
Родятся строки и умрут.
Возникнут строки и погибнут.
Их дни сотрут,
Века сотрут -
И памятник себе воздвигнут.
Я знаю, бронза простоит.
Но слава, коль она не вечна,
Сгорает, как метеорит.

Мы всё ещё стремимся к славе -
И мудрецы и простаки.
Потомки да простят нам слабость,
Мы заработали венки!

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

Венки нам не нужны.
Метеорит сгорит.
Но ты, наш правнук,
Не бронзу заготовь -
Мы заработали любовь.

7.
Моя телеграмма найдёт меня.
Моя телеграмма - кусок огня.
Она отстучит, отзвенит монисто
В пульсах девчонок-телеграфисток.
Листок подадут.
Распишусь спеша.
Сломаю кончик карандаша.

Я выйду на площадь,
Я выйду к вокзалу.
У поезда встану -
С площадки спущусь.
И обниму.
- Ожидала?
- Да.
- Не отдам никому.

Отбиты аллеи. Враги перебиты.
Но есть на Земле еще мирные битвы,
Где нет орденов и не льётся кровь,
Где гибнет или живёт любовь.

Мы пойдём по городу рука об руку,
Под нами, как мячик, будет прыгать Земля,
А вечером счастье обхватит обручем
И бросит влюблённых под тополя.
На скамейку сядем.
Подальше от сторожа.
Заговорим, приглушив голоса.

В белом небе растает стремительный след,
Над площадкой смешаются пыль и дым.
Кто-то ночью уходит на тысячу лет,
Кто-то будет любить через тысячу лет,
Из двоих лишь один остаётся живым.

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

Попробуйте,
Только скажите: они любили.
Неправда!
Любят. Живут. Навсегда.

8.
А Земля не хотела пускать.
- Не пущу! Не пущу. Не отдам.
Я земным притяженьем своим
Удержу тебя на года.
Человек, ты будешь моим.
Только как же так - не уйти?
Он прижался к её груди
Он зарылся лицом в траву.
И ушёл.
Туда.
В синеву...

В небо ввинчены тополя.
По антенам струится ток,
И бежит по орбите Земля,
Словно хочет сказать:
- Сынок!

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

Это значит на день
и на два.
Может быть, на года -
На всегда не уходит никто,
Даже тот, кто ушёл навсегда...
Возвращается человек
И навстречу летит Земля,
В небо ввинчены тополя,
По антенам струится ток...

(Е.Лучковский "Про Любовь")

воскресенье, 31 мая 2015 г.

vk.com: как запостить картинку на стену

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

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

С точки зрения вконтакта эта процедура состоит из следующих этапов:
  1. авторизация в их сети и запрос разрешения на действие от имени пользователя;
  2. некая подготовительная работа, завершающаяся получением url, на который следует отправить изображение;
  3. закачка изображения на предоставленный вконтактом ресурс, адрес которого получен в п.2;
  4. сохранение изображения вконтакте;
  5. формирование сообщения на стену, содержащего ссылку на сохраненное изображение.

К счастью, вконтакт предоставляет некий API, позволяющий почти все эти действия производить прямо из скриптов, выполняющихся на стороне клиента. Почти все. Кроме одного. Кроме пункта 3. И в этом заключается большая интрига.

Разберём эту схему подробнее. Прежде всего необходимо провести подготовительную работу. Не каждое приложение, умеющее работать с сокетами, может взаимодействовать с вконтактом. Требуется его предварительная регистрация в этой социальной сети. Делается это вот тут. Результатом этой регистрации будет некое число (или строка?), apiId (пусть будет 121212), которое потребуется в п.1.

Сам процесс авторизации выглядит достаточно просто. В заголовок нашей веб-страницы добавляется скрипт VK API:
<script src="//vk.com/js/api/openapi.js" type="text/javascript"></script>
Далее вызывается функция, использующая apiId, полученный при регистрации приложения:
VK.init({apiId: "121212"});

После этого производится собственно авторизация:
VK.Auth.login(
    function()
        {
        // действия в случае успешной авторизации
        },
    5);
Следует обратить внимание на цифру 5. Это на самом деле 1+4, или, проще говоря, установленный нулевой и второй флаги запрашиваемых прав доступа.

На этом пункт 1 можно считать выполненным.

Второй пункт продолжает действия в случае успешной авторизации. Он выглядит так:
VK.Api.call(
    "photos.getWallUploadServer",
    {},
    function(data)
        {
        // объект data содержит либо свойство error, описывающее ошибку,
        // либо свойство response, содержащее адрес ресурса, на который
        // следует отправлять изображение для сохранения его вконтакте:
        // data.response["upload_url"]
        }
);
Как видим, по окончании этого этапа у нас на руках оказывается некий длинный url примерно такого вида: http://c12345.vk.com/?param1=11111&param2=22222. Дальше на этот адрес следует сделать POST-запрос с параметром по имени photo, содеращим файл изображения.

Тут начинается проблема. На стороне клиента инструментарий, позволяющий межсайтовый скриптинг, достаточно ограничен. Можно воспользоваться jsonp - но он не позволяет POST-запросы. Можно создать дочернее окно с веб-формой, заполнить её и сделать сабмит - но как в эту форму передать двоичное содержимое файла, которого нет у клиента? Можно, наконец, попросить наш веб-сервер передать за нас нужный файл - но в документации сказано: запрос на получение адреса для загрузки файла и post-запрос с файлом должны осуществляться с одного ip-адреса (и к тому же, по слухам, вконтакт не любит много запросов с одного адреса).

Остаётся единственная альтернатива: использовать flash, точнее, flex-приложение (потому что последнее - бесплатно). Флэш тоже имеет свои представления о безопасности и загрузке файлов, но, к счастью, вконтакте позаботился об этом, и сайты c12345.vk.com предоставляют вполне либеральный файл crossdomain.xml:
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only"/>
<allow-http-request-headers-from domain="*" headers="*"/>
<allow-access-from domain="*" to-ports="80"/>
<allow-access-from domain="*" to-ports="443"/>
</cross-domain-policy>
Сам флэш-ролик на странице мы вольны поместить с атрибутом allowScriptAccess="always", и, таким образом, обеспечить двусторонний обмен скриптов ролика со скриптами из вмещающей страницы:
<object id="mySwf" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" allowScriptAccess="always">
    <embed
        name="mySwf"
        src="vkUpload.swf"
        width="500"
        height="500"
        allowScriptAccess="always"
        >
    </object>

Что касается внутренности флэш-ролика, то она может быть, например, такой:
<?xml version="1.0" encoding="utf-8"?>
<s:Application
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:s="library://ns.adobe.com/flex/spark"
    width="500"
    height="500"
    creationComplete="init()"
    >

<mx:Image id="myImage" width="100%" height="100%"/>

<fx:Script>
<![CDATA[

import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.display.Loader;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;

import flash.external.*;

import mx.graphics.codec.PNGEncoder;

internal var
    imageDownloader : Loader = new Loader(),
    imageByteArray  : ByteArray = null,

    imageUploadUrl  : String = "",
    imageUploader   : URLLoader = new URLLoader(),

    uploadResponse  : String = "";


public function init() : void
{
    Security.allowDomain("*");
    ExternalInterface.addCallback("processDataF", processData);

    imageDownloader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, imageDownloaderErrorHandler);
    imageDownloader.contentLoaderInfo.addEventListener(Event.COMPLETE,        imageDownloaderCompleteHandler);

    imageUploader.addEventListener(IOErrorEvent.IO_ERROR,             imageUploaderErrorHandler);
    imageUploader.addEventListener(Event.COMPLETE,                    imageUploaderCompleteHandler);
    imageUploader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, imageUploaderSecurityErrorHandler);
    imageUploader.dataFormat = URLLoaderDataFormat.TEXT;
}


public function processData (fileSourceUrl : String, uploadUrl : String) : void
{
    // сохраняем адрес выгрузки изображения
    imageUploadUrl = uploadUrl;

    // получаем изображение
    var request:URLRequest = new URLRequest(fileSourceUrl);
    try
    {
        imageDownloader.load(request);
    }
    catch (error:*)
    {
        processError("Загрузка изображения", error.message);
    }
}

private function processError (step : String, message : String) : void
{
    ExternalInterface.call("ReturnFromFlexWithError", step + "\n" + message);
}

private function processComplete (message : String) : void
{
    ExternalInterface.call("ReturnFromFlexWithSuccess", encodeURIComponent(message));
}

private function imageDownloaderErrorHandler (e : IOErrorEvent) : void
{
    processError("Процесс загрузки изображения", e.text);
}

private function imageDownloaderCompleteHandler (e : Event) : void
{
    try
    {
        var bitmap : Bitmap = Bitmap(imageDownloader.content);
        myImage.source = bitmap;

        var png:PNGEncoder = new PNGEncoder();
        imageByteArray = png.encode(bitmap.bitmapData);
    }
    catch (error:*)
    {
        processError("Окончание процесса загрузки изображения", error.message);
        return;
    }

    if (!imageByteArray)
    {
        processError("Получение данных для выгрузки изображения", "Массив пуст");
        return;
    }


    // выгружаем изображение
    var postData : ByteArray = new ByteArray();

    postData.writeMultiByte('--TESTTESTTEST\r\n', 'utf-8');
    postData.writeMultiByte('Content-Disposition: form-data; name="photo"; filename="photo.png"\r\n', 'utf-8');
    postData.writeMultiByte('Content-Type: application/octet-stream\r\n', 'utf-8');
    postData.writeMultiByte('Content-Transfer-Encoding: binary\r\n', 'utf-8');
    postData.writeMultiByte('\r\n', 'utf-8');
    postData.writeBytes(imageByteArray);
    postData.writeMultiByte('\r\n', 'utf-8');
    postData.writeMultiByte('--TESTTESTTEST--\r\n', 'utf-8');

    var request : URLRequest = new URLRequest(imageUploadUrl);
    request.method = URLRequestMethod.POST;
    request.requestHeaders.push( new URLRequestHeader( 'Content-Type', 'multipart/form-data; boundary=TESTTESTTEST' ) );
    request.data = postData;
    try
    {
        imageUploader.load(request);
    }
    catch (error:*)
    {
        processError("Начало процесса выгрузки изображения", error.message);
        return;
    }
}

private function imageUploaderErrorHandler (e : IOErrorEvent) : void
{
    processError("Процесс выгрузки изображения, ввод-вывод", e.text);
}

private function imageUploaderSecurityErrorHandler (e : SecurityErrorEvent) : void
{
    processError("Процесс выгрузки изображения, безопасность", e.text);
}

private function imageUploaderCompleteHandler (e : Event) : void
{
    processComplete(String(imageUploader.data));
}

]]>
</fx:Script>

</s:Application>
Схема взаимодействия этого ролика со скриптом вмещающей страницы проста. Скрипт со страницы вызывает метод ролика:
document["mySwf"].processDataF(АдресИзображенияДляЗагрузки, АдресСервераВконтакте);
Этот метод по результатам своего труда либо при ошибке передает управление в функцию:
function ReturnFromFlexWithError(data)
{
    alert("Ошибка при работе через Flex!\n" + data);
}
либо - при успехе - в функцию:
function ReturnFromFlexWithSuccess(data)
{
    var
        jsonData = JSON.parse(decodeURIComponent(data));
    // действия в случае успешной загрузки изображения
    // особую ценность представляют jsonData.photo, jsonData.server, jsonData.hash
}
Тут стоит упомянуть забавную тонкость. Если не uri-кодировать строку, возвращаемую роликом в основной скрипт, то браузер выдает ошибку синтаксиса. Поэтому приходится передавать закодированную строку, и декодировать её на стороне javascript-а.

Вернемся к нашей схеме. Получив на предыдущем этапе значения photo, server и hash мы можем сохранить загруженное фото:
VK.Api.call(
    "photos.saveWallPhoto",
    {photo: jsonData.photo, server: jsonData.server, hash: jsonData.hash},
    function(data)
        {
        // объект data содержит либо свойство error, описывающее ошибку,
        // либо свойство response, содержащее идентификатор сохраненного изображения
        // data.response[0].id

        }
);

Осталось выполнить последний пункт: разместить на стене изображение, содержащее фото. Это делается командой:
VK.api(
    "wall.post",
    {message: "текст сообщения", attachments: data.response[0].id},
    function (data)
        {
        // объект data содержит либо свойство error, описывающее ошибку,
        // либо свойство response, содержащее результат операции
        }
);

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

А это - в качестве иллюстрации вышеизложенного:
http://это-очередной-унылый-домен-из-кириллической-зоны.рф/web/js/z150525-00.xml

среда, 6 мая 2015 г.

ШТРИХ-ФР-К: одна маленькая особенность

Если при печати чека из 1С драйвер фискального регистратора вдруг заявляет "При печати чека произошла ошибка. Чек не напечатан на фискальном регистраторе. Дополнительное описание: A3h, Некорректное состояние ЭКЛЗ", не надо ему так уж прямо безоговорочно верить. Лучше поверить программе "Тест драйвера", которая показывает, что всё нормально, и проводит чек без всяких ошибок.

Я перетряхнул все кабели, обновил версию драйвера с 4.9 на 4.12 и обратно, но дело оказалось вовсе не в этом. В таких случаях надо включить ведение логов драйвера (это делается либо в программе "Тест драйвера" - кнопка "Настройка свойств" - кнопка "Дополнительные параметры" - галочка "Вести лог", либо в самой 1С в свойствах подключаемого оборудования - в зависимости от того, какая программа глючит), и тогда будет видно различие логов "больного" чека:
...
[06.05.2015 12:39:38.365] [ info]: TFiscalPrinter ------------------------------------------------------------
[06.05.2015 12:39:38.365] [debug]: TFiscalPrinter Команда: 8Dh, Открыть чек
[06.05.2015 12:39:38.365] [ info]: TFiscalPrinter ------------------------------------------------------------
...
[06.05.2015 12:39:38.928] [debug]: Tole1Cst OpenCheck(CheckNumber: 972; SessionNumber: 309): True
[06.05.2015 12:39:38.928] [debug]: Tole1Cst RetValue:True
[06.05.2015 12:39:38.928] [debug]: Tole1Cst Invoke: 0x00000000 (Операция успешно завершена)
...
[06.05.2015 12:39:39.271] [ info]: TFiscalPrinter ------------------------------------------------------------
[06.05.2015 12:39:39.271] [debug]: TFiscalPrinter Команда: 85h, Закрытие чека
[06.05.2015 12:39:39.271] [ info]: TFiscalPrinter ------------------------------------------------------------
[06.05.2015 12:39:39.271] [debug]: TPrinterProtocol -> 05
[06.05.2015 12:39:39.271] [debug]: TPrinterProtocol <- 15
[06.05.2015 12:39:39.271] [debug]: TPrinterProtocol -> ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ
[06.05.2015 12:39:39.271] [debug]: TPrinterProtocol -> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[06.05.2015 12:39:39.271] [debug]: TPrinterProtocol -> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[06.05.2015 12:39:39.271] [debug]: TPrinterProtocol -> 00 00 00 00 00 00 00 00 00 00 00 00 00 68
[06.05.2015 12:39:39.271] [debug]: TPrinterProtocol <- 06
[06.05.2015 12:39:39.334] [debug]: TPrinterProtocol <- 02
[06.05.2015 12:39:39.334] [debug]: TPrinterProtocol <- 02
[06.05.2015 12:39:39.334] [debug]: TPrinterProtocol <- 85 A3 24
[06.05.2015 12:39:39.334] [debug]: TPrinterProtocol -> 06
[06.05.2015 12:39:39.334] [error]: TFiscalPrinter Ошибка: (163, A3h) Некорректное состояние ЭКЛЗ
[06.05.2015 12:39:39.334] [error]: TFiscalPrinter Ошибка: 163 Некорректное состояние ЭКЛЗ
...

и "здорового":
...
[06.05.2015 12:58:23.819] [ info]: TFiscalPrinter ------------------------------------------------------------
[06.05.2015 12:58:23.819] [debug]: TFiscalPrinter Команда: 8Dh, Открыть чек
[06.05.2015 12:58:23.819] [ info]: TFiscalPrinter ------------------------------------------------------------
...
[06.05.2015 12:58:24.772] [ info]: TFiscalPrinter ------------------------------------------------------------
[06.05.2015 12:58:24.772] [debug]: TFiscalPrinter Команда: 80h, Продажа
[06.05.2015 12:58:24.772] [ info]: TFiscalPrinter ------------------------------------------------------------
..
[06.05.2015 12:58:24.866] [debug]: Tole1Cst PrintFiscalString: True
[06.05.2015 12:58:24.866] [debug]: Tole1Cst RetValue:True
[06.05.2015 12:58:24.866] [debug]: Tole1Cst Invoke: 0x00000000 (Операция успешно завершена)
...
[06.05.2015 12:58:25.209] [ info]: TFiscalPrinter ------------------------------------------------------------
[06.05.2015 12:58:25.209] [debug]: TFiscalPrinter Команда: 85h, Закрытие чека
[06.05.2015 12:58:25.209] [ info]: TFiscalPrinter ------------------------------------------------------------
[06.05.2015 12:58:25.209] [debug]: TPrinterProtocol -> 05
[06.05.2015 12:58:25.209] [debug]: TPrinterProtocol <- 15
[06.05.2015 12:58:25.209] [debug]: TPrinterProtocol -> ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ ХХ
[06.05.2015 12:58:25.209] [debug]: TPrinterProtocol -> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[06.05.2015 12:58:25.209] [debug]: TPrinterProtocol -> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[06.05.2015 12:58:25.209] [debug]: TPrinterProtocol -> 00 00 00 00 00 00 00 00 00 00 00 00 00 9A
[06.05.2015 12:58:25.209] [debug]: TPrinterProtocol <- 06
[06.05.2015 12:58:25.288] [debug]: TPrinterProtocol <- 02
[06.05.2015 12:58:25.288] [debug]: TPrinterProtocol <- 08
[06.05.2015 12:58:25.288] [debug]: TPrinterProtocol <- 85 00 01 00 00 00 00 00 8C
[06.05.2015 12:58:25.288] [debug]: TPrinterProtocol -> 06
...
[06.05.2015 12:58:27.991] [debug]: Tole1Cst CloseCheck: True
[06.05.2015 12:58:27.991] [debug]: Tole1Cst RetValue:True
[06.05.2015 12:58:27.991] [debug]: Tole1Cst Invoke: 0x00000000 (Операция успешно завершена)
...

Как видим, если между открытием чека и закрытием не было операции "Продажа" (или, например, как говорят опытные люди, если в незакрытом чеке продажа была отсторнирована), то в момент закрытия чека его открытая электронная копия на ЭКЛЗ отсутствует, и возникает вышеуказанная ошибка A3h.

четверг, 2 апреля 2015 г.

Ubuntu: как сконвертировать Animated GIF в FLV

В два этапа:

1. Раскидываем gif-ку на кучку отдельных изображений:
convert исходный_файл.gif gif%05d.png

2. Собираем из изображений видеофайл:
avconv -i gif%05d.png результат.flv

P.S. Вообще-то в сети полно решений с использованием ffmpeg, но в штатных репозиториях своей Ubuntu 14.10 (Utopic Unicorn) я эту штуку не нашел.

понедельник, 30 марта 2015 г.

Slackware: настраиваем pptp

Вот уже несколько месяцев наша контора топчется вокруг такой ситуации. Есть головной офис, скажем, в городе Амстердам, есть филиал (в котором, в частности, нахожусь я), расположенный, например, в Будапеште, есть агентства в Ванкувере или там в Виннипеге, неважно. Будапешт с Амстердамом соединен некой корпоративной сетью, а Ванкувер и Виннипег с Будапештом - чтобы не вдаваться в подробности, обычным "интернетом". В Амстердаме находится группа серверов, которые по корпоративной сети доступны, а из интернета - не очень. Возникает вопрос - как тому же Виннипегу сделать доступ на эти серверы?

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

Второй вариант - поднять сервер PPTP на компьютере, который видит как "интернет", так и сеть Амстердама. (На самом деле всё устроено немного сложнее, тут главное, что этот компьютер хоть и обладает одним сетевым интерфейсом, eth0, зато его прекрасно видно из Ванкувера, а он, в свою очередь, имеет возможность достучаться до всех, кого нужно)

Итак, на этом волшебном компьютере под управлением Slackware:

1. Разворачиваем poptop, оказавшийся тем самым pptpd, установить который предлагал каждый второй мануал командой apt-get install pptpd. Ну, у нас тут не дебиан, так что скомпилируем из исходников.

2. Настраиваем pptpd: из каталога samples исходников копируем файл pptpd.conf в /etc и прописываем в нем две опции (настройки виртуальной сети - свой адрес в ней и диапазон адресов клиентов):
localip 192.168.0.1
remoteip 192.168.0.200-234

3. Настраиваем pptpd: из каталога samples исходников копируем файл options.pptpd в каталог /etc/ppp/, комментируем в нем опцию proxyarp (не знаю, правда, зачем, это была безуспешная попытка избавиться от сообщения Cannot determine ethernet address for proxy ARP) и прописываем в нем две опции (адреса dns и wins-серверов амстердамской сети):
ms-dns A1.A1.A1.A1
ms-wins A2.A2.A2.A2

4. Настраиваем pptpd: из каталога samples исходников копируем файл chap-secrets в каталог /etc/ppp/ и указываем там учетку, которой разрешено входить в виртуальную сеть (заодно можно указать адрес, который будет ей выделяться), формат каждой записи примерно такой:
логин[табуляция]*[табуляция]пароль[табуляция]*

6. Настраиваем сервер: разрешаем форвардинг ip-пакетов на уровне ядра. Это делается так. В файл /etc/sysctl.conf прописываем строку:
net.ipv4.ip_forward=1
затем эту опцию подсовываем в ядро командой:
sysctl -p

7. Настраиваем iptables, но, видимо, это не очень обязательно, если доселе они не использовались. Так или иначе, не повредит:
# Пропускать все пакеты с интерфейсов ppp*, например ppp0
/usr/sbin/iptables -t filter -A INPUT  -i ppp+ -j ACCEPT
/usr/sbin/iptables -t filter -A OUTPUT -o ppp+ -j ACCEPT

# Пропускать входящие соединения на порт 1723 (PPTP)
/usr/sbin/iptables -t filter -A INPUT -p tcp --dport 1723 -j ACCEPT

# Пропускать все пакеты GRE
/usr/sbin/iptables -t filter -A INPUT  -p 47 -j ACCEPT
/usr/sbin/iptables -t filter -A OUTPUT -p 47 -j ACCEPT

# Включить форвардинг IP
/usr/sbin/iptables -t filter -F FORWARD
/usr/sbin/iptables -t filter -A FORWARD -j ACCEPT

# Включить NAT для интерфейсов eth0 и ppp*
/usr/sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
/usr/sbin/iptables -t nat -A POSTROUTING -o ppp+ -j MASQUERADE

Осталось настроить vpn-клиентов. В моём случае это машины с WinXP на борту, так что там всё просто: из-под учетки с административными правами в Панели управления в задаче "Сетевые подключения" вызвать мастер установки нового подключения и с его помощью всё установить. Правда, маршрутизация там оказалась хитрая: клиент лезет в vpn только если не может найти нужный ресурс в локальной сети, но, в крайнем случае можно указать статический маршрут:
route add A.0.0.0 mask 255.0.0.0 192.168.0.200
Впрочем, судя по разговорам умных людей, эта проблема имеет более изящное решение.

Да, и ещё одно. Надо отметить, что в самом начале пути меня постигло некоторое разочарование. Не все vpn-серверы оказались одинаково полезны. В частности, к установленному с опрометчивой самонадеянностью tinc-у штатные клиенты WinXP цепляться не будут. Дело, как я понял, в том, что tinc и pptp - разные протоколы, и майкрософт поддерживает "из коробки" как раз pptp.

Литература:
PPTP server (Русский)
Настраиваем VPN сервер. Часть 3 - PPTP. Платформа Linux.

UPD 2015-08-03: Если вдруг зачем-то понадобилось сменить порт tcp1723, на котором работает pptp, то можно поступить так:
a) на сервере (под линуксом) мапим порт:
iptables -t nat -A PREROUTING -p tcp --dport 11723 -j REDIRECT --to-ports 1723
б) на клиенте (под виндой) открываем regedit и ищем параметр TcpPortNumber. У меня он нашелся в ветке:
HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\0005
Меняем его значение с 1723 на 11723 и перезагружаем компьютер.