воскресенье, 20 декабря 2015 г.

Linux: перекодировать файл из win-1251+CRLF в utf-8+LF

cat Файл-win1251.txt | iconv -f windows-1251 -t utf-8 | tr -d '\r' > Файл-utf8.txt

вторник, 24 ноября 2015 г.

Windows XP: FireFox и аппаратное ускорение

Допустим, есть компьютер под управлением Windows XP с графической системой Intel Q45/Q43 Express на борту, и есть браузер Mozilla FireFox, в котором галочка "Use hardware acceleration when available" вроде как стоит. При этом информация "about:support" показывает "GPU Accelerated Windows: 0/1 Basic". А хочется 1/1.

Помогло следующее:

1) Обновил драйверы видео до последней версии

2) В настройках браузера (about:config) поставил две опции:
gfx.direct2d.force-enabled=true
layers.acceleration.force-enabled=true

3) Перезапустил FireFox.

Источники:
Force Enable Hardware Acceleration in Firefox
Blocklisting/Blocked Graphics Drivers

Slackware: dot и graphviz

Начитался тут баша и открыл для себя язык описания графов по имени dot. Соответственно, возникло непреодолимое желание этот самый dot пощупать. Решил взгромоздить на свой Slackware некий софт graphviz v2.38, собрав его из исходников. Дальше начались чудеса.

Во-первых, заглючила команда make, вывалившись с сообщением error: 'tsrm_ls' undeclared. Решение нашлось, но, по-моему, на китайском языке. К счастью, буквы, которые надо вводить в компьютер, остались английскими, так что сориентироваться можно. В общем, надо отредактировать файл tclpkg/gv/gv_php_init.c, добавив в пару функций по строчке:
static size_t gv_string_writer (GVJ_t *job, const char *s, size_t len)
{
    TSRMLS_FETCH(); // <- добавили
    return PHPWRITE(s, len);
}
 
static size_t gv_channel_writer (GVJ_t *job, const char *s, size_t len)
{
    TSRMLS_FETCH(); // <- добавили
    return PHPWRITE(s, len);
}


На этом приключение не закончилось. Впервые на моей памяти заглючила также команда make install. На этот раз ошибка выглядела примерно так: fatal error: QtGui/qwidget.h: No such file or directory. Оказывается, требуется поправить файл /cmd/gvedit/Makefile: найти там строку примерно такого вида:
INCPATH       = -I/usr/lib64/qt/mkspecs/linux-g++ -I. -I/usr/lib64/qt/include/QtCore -I/usr/lib64/qt/include/QtGui -I/usr/lib64/qt/include -I../../lib/gvc -I../../lib/common -I../../lib/pathplan -I../../lib/cgraph -I../../lib/cdt -I../.. -I.
и вымарать из неё пару опций:
INCPATH       = -I/usr/lib64/qt/mkspecs/linux-g++ -I. -I/usr/lib64/qt/include -I../../lib/gvc -I../../lib/common -I../../lib/pathplan -I../../lib/cgraph -I../../lib/cdt -I../.. -I.
После этого всё, наконец, установилось.

Штука оказалась довольно прикольной. Например, можно создать вот такой текстовый файл sample.gv:
digraph myFirstGraph {
    edge [color=blue]
    a;
    b [shape=box label="Ку-ку"];
    c;
    d;
    a -> b [label="некая связь"];
    subgraph g1 {
        edge [dir=none]
        b -> c;
        b -> d;
    }
}
натравить на него команду:
dot -Tpng -osample.png sample.gv
и получить симпатичную картинку:
Ну не чудо ли!

четверг, 19 ноября 2015 г.

Slackware: загадочный VirtualBox

Сегодня как-то нехорошо себя повёл VirtualBox. При попытке запустить виртуальную машину командой:
virtualbox --startvm WinXP
он тяжело и надолго задумался, а потом закрылся с сообщением:
ICE default IO error handler doing an exit(), pid = 12345, errno = 32
Проверка диска при помощи fsck ничего интересного не дала.
Почему-то помогла очистка папки ~/VirtualBox VMs/WinXP/Logs

понедельник, 26 октября 2015 г.

Linux: PostgreSQL 9.x и сертификаты

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

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

1) адрес или имя сервера баз данных, по которому будет стучаться клиент, например, myserver

2) имя системного пользователя, который пытается авторизоваться, например, sysUser

3) имя пользователя базы данных, под которым пытается авторизоваться системный пользователь, например, pgUser

При этом затрагиваются следующие каталоги и файлы конфигурации:

1) На стороне сервера: файлы postgresql.conf, pg_hba.conf и pg_ident.conf. Эти файлы в зависимости от сборки могут располагаться в разных местах. Например, на дебиане они обнаружились в каталоге /etc/postgresql, а на слакваре при установке из исходных кодов обосновались в рабочем каталоге /usr/local/pgsql/data

2) На стороне клиента: каталог ~/.postgresql/, в который должна быть положена пара ключ-сертификат, использующаяся для проверки подлинности пользователя.

Итак, настраиваем все эти сертификаты. Так как я - по натуре нищеброд, то сертификаты у меня будут самоподписанные, а создавать их буду бесплатной утилитой openssl.

Сначала генерируем корневой сертификат, которым будем подписывать сертификаты сервера и клиента:
# генерируем приватный ключ
openssl genrsa -out root.key 1024

# создаем самоподписанный корневой сертификат
openssl req -new -x509 -days 1826 -key root.key -out root.crt
При этом будут заданы всякие вопросы, от ответов, я так понял, мало что зависит.

Затем при помощи этого корневого сертификата создаем сертификат сервера:
# генерируем приватный ключ
openssl genrsa -out server.key 1024

# создаем запрос сертификата
# тут надо внимательно отнестись к заполнению полей:
# в поле Common Name (CN) надо указать адрес или имя сервера баз данных
# (например, myserver)
openssl req -new -utf8 -key server.key -out server.csr

# из запроса создаём сертификат сервера, подписанный корневым сертификатом
openssl x509 -req -days 730 -in server.csr -CA root.crt -CAkey root.key -out server.crt

У нас получилось три ценных файла: root.crt, server.key, server.crt. Их нужно запихнуть в рабочий каталог postgresql (тот самый что-то-там/data, в котором при установке СУБД делалась команда initdb). При этом на файл server.key налагаются суровые ограничения по безопасности: он должен иметь владельцем пользователя, под которым работает СУБД, и уровень доступа -rw-------.

Теперь сгенерируем сертификат для клиента:
# генерируем приватный ключ
openssl genrsa -out postgresql.key 1024

# создаем запрос сертификата
# тут тоже надо внимательно отнестись к заполнению полей:
# в поле Common Name (CN) следует указать имя системного пользователя,
# для которого создаётся сертификат
# (например, sysUser)
openssl req -new -utf8 -key postgresql.key -out postgresql.csr

# из запроса создаём сертификат клиента, подписанный корневым сертификатом
openssl x509 -req -days 730 -in postgresql.csr -CA root.crt -CAkey root.key -out postgresql.crt -CAcreateserial

После этой процедуры два файла, postgresql.key и postgresql.crt, закидываем клиенту в папку ~/.postgresql/ и у файла postgresql.key опять-таки выставляем правильного владельца и права -rw-------.

Стоит отметить, что содержимое получившихся сертификатов, в частности, заполнение поля CN можно всегда посмотреть командой:
openssl x509 -in postgresql.crt -text -noout -nameopt oneline,-esc_msb,utf8

Так или иначе, осталось написать правильные настройки сервера баз данных:

1) в файле postgresql.conf проверяем, что включена опция ssl = on

2) прописываем в pg_hba.conf строку:
#TYPE    DATABASE  USER    ADDRESS   METHOD
hostssl  all       pgUser  myserver  cert clientcert=1 map=myMap
Тут мы указали, что соединение должно быть защищено ssl, для авторизации следует использовать сертификат клиента и при сопоставлении системного имени и имени пользователя баз данных будет использоваться запись с меткой myMap из файла pg_ident.conf

3) в файле pg_ident.conf прописываем строку:
#MAPNAME  SYSTEM-USERNAME  PG-USERNAME
myMap     sysUser          pgUser
На самом деле, как я понял, этот файл содержит довольно простую информацию: какому системному пользователю под каким именем СУБД разрешается подключаться к серверу. То есть, если пользователю позволено подключаться под разными именами, pg_ident.conf будет выглядеть так:
#MAPNAME  SYSTEM-USERNAME  PG-USERNAME
myMap     sysUser          pgUser1
myMap     sysUser          pgUser2

4) перезапускаем сервер PostgreSQL.

Процесс авторизации по этой схеме выглядит, насколько я понял, так:

1) Устанавливается защищенное соединение при помощи сертификатов server.key + server.crt

2) От клиента сервер получает сертификат postgresql.crt, из которого выясняет системное имя пользователя. Имя пользователя базы данных, под которым хочет зайти системный пользователь, указывается явно в строке подключения.

3) По имени пользователя базы данных, имени сервера и режиму соединения (hostssl) определяется строка в файле pg_hba.conf, из которой выясняется имя метки в файле pg_ident.conf.

4) По метке в файле pg_ident.conf устанавливается, может ли данный системный пользователь авторизоваться в СУБД под данным пользователем баз данных.

Вообще говоря, требование размещения клиентских сертификатов в каталоге ~/.postgresql/ не является таким уж обязательным. Просто их там по умолчанию ищет библиотека libpq. В произвольных же скриптах можно указывать явно, какие файлы использовать в качестве пары ключ-сертификат. Вот, например, скрипт на Python-е:
import psycopg2

connString = "host=myserver"             + \
             " user=pgUser"              + \
             " dbname=postgres"          + \
             " sslcert=./postgresql.crt" + \
             " sslkey=./postgresql.key"

conn = psycopg2.connect(connString)

вторник, 15 сентября 2015 г.

eToken: создать сертификат при помощи openssl

Дано:

1) USB-ключ eToken PRO (JAVA).
2) Операционная система Windows XP с установленным в ней eToken PKI Client-ом.
3) Операционная система Slackware 14.1
4) Центр сертификации Microsoft Active Directory Certificate Services со своим веб-интерфейсом.
5) Некий веб-ресурс, который умеет общаться по https с использованием сертификатов, выданных центром сертификации из п.4.

Требуется:

1) Получить сертификат в центре сертификации
2) Записать полученный сертификат на eToken
3) Подключиться к требуемому веб-ресурсу из-под Windows XP с использованием браузера Internet Explorer.

Решение:

Вообще-то, можно было бы воспользоваться веб-интерфейсом центра сертификации. Но есть маленькая проблема: этот центр с некоторых пор перестал воспринимать Windows XP.

Выглядит это так. Если попытаться при помощи IE8 сделать запрос сертификата через задачу "Advanced certificate request", центр сертификации присылает HTTP-ответ, в котором Content-length указано примеро 90кб, а реальная длина ~30кб, причем невооруженным глазом видно, что ответ оборван посередине какого-то vb-скрипта. Не знаю, с чем это связано, тем более, что если подменять User-agent браузера на другие варианты (например, при помощи чудесной утилиты UAPick), то HTTP-ответы формируются нормально.

Можно было бы при помощи подмены User-agent бы притвориться, скажем, IE9+Win7, но этот вариант не подходит: в современных системах работа с сертификатами на стороне клиента реализуется при помощи CertEnroll API, а в Windows XP для этой цели использовалось XEnroll API. Соответственно, если центр сертификации считает клиента работающим под новой ОС, он отправляет ему скрипты, которые выполняются с ошибками.

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

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

1. Генерируем 1024-битный ключ (eToken на ключи с большей длиной у меня почему-то ругается):
openssl genrsa -out 0000.key 1024

2. При помощи полученного ключа 0000.key делаем запрос сертификата:
openssl req -new -utf8 -config my.conf -key 0000.key -out 0000.csr
Тут обратим внимание на две опции: -utf8 позволяет заполнять поля запроса русскими буквами, а -config my.conf позволяет в файле my.conf выполнить "тонкую настройку", в частности, у меня этот файл выглядит так:
[req]
default_bits           = 1024
distinguished_name     = req_distinguished_name
req_extensions         = req_ext

[req_distinguished_name]
countryName                    = Country Name (2 letter code)
countryName_default            = RU
countryName_min                = 2
countryName_max                = 2

organizationalUnitName         = Organizational Unit Name (eg, section)

commonName                     = Common Name (eg, YOUR name)
commonName_max                 = 64

[req_ext]
extendedKeyUsage = clientAuth

3. Далее открываем получившийся файл 0000.csr в блокноте и копируем его содержимое в соответствующее поле ввода на веб-странице центра сертификации. Этот центр его переваривает и выдаёт сертификат, скажем, 0000.cer

4. Ключ 0000.key и сертификат 0000.cer сливаем в один файл 0000.p12:
openssl pkcs12 -export -out 0000.p12 -inkey 0000.key -in 0000.cer
и закидываем этот файл на eToken - там в PKI-клиенте есть команда "Импорт сертификата".

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

Литература:
http://www.websense.com/support/article/kbarticle/How-to-use-OpenSSL-and-Microsoft-Certification-Authority
https://www.openssl.org/docs/manmaster/apps/x509v3_config.html

среда, 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 и перезагружаем компьютер.

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

Windows: коварный Mozilla Thunderbird

Ситуация: есть Mozilla Thunderbird версии, скажем, 31.5.0, на ней настроена некая учетная запись электронной почты. Эта учетка может принимать сообщения, но отправлять - ни в какую. Выглядит это так. Пишем сообщение, нажимаем "Отправить", программа пишет "Компоновка сообщения...", затем на пару секунд задумывается, и выдаёт "Ошибка отправки сообщения. Проверьте настройки своей учетной записи, параметры связи с сервером и т.п." Так вот, эта подсказка - наглая ложь. Подслушка сниффером (например, packetyzer-ом) показывает, что почтовик даже не пытается стукнуться к серверу. И, что тоже интересно, при попытке закрыть неудавшееся сообщение, выдаётся предложение сохранить его в черновики, но сохранение также оказывается безуспешным. При этом, если запустить Thunderbird на той же машине, но под другим пользователем Windows - всё работает.

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

Но нет худа без добра. Зато выяснилось, как устроить журналирование общения с почтовым сервером. Делается это при помощи такого батника:
set NSPR_LOG_MODULES=SMTP:4
set NSPR_LOG_FILE=C:\temp\log_smtp.txt
"%ProgramFiles%\Mozilla Thunderbird\thunderbird.exe"

понедельник, 16 февраля 2015 г.

Windows batch file: массовое переименование файлов

Понадобилось тут переименовать несколько файлов. Например, было list-март-01.txt, должно стать list-апрель-01.txt. Верный и надежный Far manager в этот раз почему-то не предоставил очевидного решения, как в переименовываемых файлах заменить одну подстроку на другую, поэтому я решил попытать счастья в написании файла .bat:

@ECHO OFF
setlocal enableDelayedExpansion

SET workingPath=c:\temp\
SET filesToRename=list-*
SET stringToSearch=март
SET stringToReplace=апрель

cd %workingPath%
ECHO working path set to %workingPath%

FOR /f "delims=^T" %%f IN ('dir /B %filesToRename%') DO (
    call :myFunc "%%f"
)
GOTO End


:myFunc

SET oldFileName=%1
SET newFileName=!oldFileName:%stringToSearch%=%stringToReplace%!
ren %oldFileName% %newFileName%
ECHO %oldFileName% renamed to %newFileName%
GOTO End


:End

В процессе работы выяснилось много удивительных вещей. Например, что внутри цикла FOR почему-то не срабатывает присвоение SET. Или что команды set и dir обладают массой интересных ключей (в частности, первая при помощи ключа /A умеет вычислять арифметические выражения, а вторая при указании /B возвращает чистые имена файлов без путей). Или что переход по метке может принимать параметр.

В общем, есть, над чем подумать.

четверг, 15 января 2015 г.

Debian: обновление с 6.0 (squeeze) до ...

А вот не знаю, до чего. Попался мне на глаза мануал Обновление с Debian 6.0 (squeeze), и решил я, что пора.

Ну, ясное дело, не обошлось без приключений. Замена в /etc/apt/sources.list всех "squeeze" на "stable" и выполнение простых команд:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
привело к тому, что третья команда вывалилась с примерно такой ошибкой:
dpkg: предупреждение: «ldconfig» отсутствует в каталогах, перечисленных в PATH, или не является исполняемым
dpkg: предупреждение: «start-stop-daemon» отсутствует в каталогах, перечисленных в PATH, или не является исполняемым
dpkg: ошибка: в каталогах PATH не найдено 2 ожидаемые программы или исполняемых файла
Замечание: В PATH суперпользователя обычно должны присутствовать /usr/local/sbin, /usr/sbin и /sbin
E: Sub-process /usr/bin/dpkg returned an error code (2)
Что с этим делать, совершенно непонятно. Правда, удалось обойти эту непонятную ошибку командой:
su
apt-get dist-upgrade
но, чует моё сердце, ещё придется править /etc/profile.

Ладно, обновление прошло, пытаюсь запустить иксы. Иксы стартуют, но ни клавиатура (PS/2), ни мышь (тоже PS/2) не работают. Мышь, правда, светится, а вот на клавиатуре не зажигаются даже лампочки. Опять лезу гуглить. Натыкаюсь на совет прописать в файле /etc/X11/xorg.conf такое:
Section "ServerFlags"
    Option "AutoAddDevices" "False"
EndSection
Такого файла у меня нет. Ладно, создаю, прописываю, перезагружаюсь. Мышь заработала, клавиатура - нет. Гуглю дальше. Оказывается, надо установить пакеты:
su
apt-get install xserver-xorg-input-kbd
apt-get install xserver-xorg-input-mouse
второй из них, правда, у меня уже есть, наверно, поэтому мышь заработала раньше клавиатуры. В общем, поставил xserver-xorg-input-kbd, пока больше проблем не заметил. Но наверняка ещё будут.