Выход из X через консоль (и желательно удаленно)

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

И оказалось, что какого-то единственного универсального способа нет.

Проверить runlevel

Runlevel или уровень запуска — это программная конфигурация системы, которая позволяет запускать только выбранную группу процессов на определенном этапе. Их до 10, но нас интересует уровень 3 — многопользовательский (консольный) режим, и уровень 5 (в Slackware — 4), многопользовательский графический режим, в котором X-server запускается по умолчанию.

Если система находится на уровне 4 (5), то способы как либо пришибить иксы могут не сработать, иксы перезапустятся. С уровня 3 можно запустить X-сервер вручную, для этого надо в консоли ввести (обычно) startx. Если система на уровне 3, то иксы сравнительно легко прибить (см. ниже).

Визуально уровень запуска обычно определить легко. 3 — после загрузки ОС будет консольное приглашение ввести логин и пароль, например:

Welcome to Linux 4.4.14-smp (tty1)

wolfsсhanze login:

На уровне 4 (5) на экране будет предложение ввести логин/пароль, но уже в иксовой форточке.
Правда, некоторые не очень популярные дистрибутивы хитрят. Например, Puppy Slacko запускается на уровне 3, а X-сервер вызывает уже из своих инициализационных скриптов.

Можно проверить runlevel и в консоли/терминале:

runlevel

Вывод:

N 3

или

who -r

Вывод:

run-level 3 2020-01-28 07:12 last=S

Переключить runlevel

Неверное, самый универсальный способ завершить работу X-server на лету и через консоль, это переключить runlevel. Команда должна быть выполнена от root.

— Для Slackware:

init 3

— Для дистрибутивов с systemd:

systemctl isolate runlevel3.target

Вернуться в иксы.

Для Slackware:

init 4

— Для дистрибутивов с systemd:

systemctl isolate graphical.target

Переключить runlevel по умолчанию (при старте системы)

— Для Slackware:

1. Под root запускаем mc и идем в /etc
2. Ищем там файл inittab и открываем его в редакторе.
3. Ищем строчки:

# Default runlevel. (Do not set to 0 or 6)
id:4:initdefault:

Они обычно в начале файла.

4. Меняем 4 на 3 и сохраняем файл. Если надо X при старте — меняем 3 на 4. Если что, обычно в файле есть комментарий-подсказка (на буржуйском).

— Для дистрибутивов с systemd:

Чтоб X был выключен по умолчанию:

systemctl set-default runlevel3.target

Чтоб X по умолчанию был включен:

systemctl set-default multi-user.target

Подробнее почитать о runlevel

Runlevel в Unix/Linux Копия

Другие способы завершить X-сервер через консоль.

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

— Придушить X-сервер совсем:

killall Xorg

— Более аккуратно придушить иксы (для систем с systemd). Надо отправить команду завершения оконному менеджеру.

В общем виде:

systemctl stop display-manager.service

Вместо display-manager.service подставляем свой оконный менеджер (наверное, не все поддерживают, но у меня systemd нет, так что не тестировал):

systemctl stop gdm

— Способ для xfce:

xfce4-session-logout --logout --display :0.0

Корректно срабатывает только с локальной консоли. Удаленно может не работать.

Еще про способы выйти в «чистую» консоль из иксов

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

Для этого надо нажать Ctrl+Alt+F2F6 и вам откроется чистый терминал. На Ctrl+Alt+F7 обычно сидят сами иксы, и таким образом, можно к ним вернуться. А первый терминал (Ctrl+Alt+F1) иксы занимают под служебные нужды. В некоторых системах для выхода из иксов срабатывает такой способ:

1. Переключиться в первую консоль (Ctrl+Alt+F1)
2. Нажать Ctrl+C/Ctrl+Break

В некоторых системах для выхода из иксов может сработать комбинация Ctrl+Alt+Backspace

В системах, запускающихся в графическом runlevel по умолчанию, это можно использовать для перезагрузки графического окружения, если X зависли (как soft-restart в Windows 98). Так же можно использовать и команду killall Xorg

Об опасности использования eval в bash-скриптах.

Преамбула

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

Исходная задача

Имелся следующий набор данных в виде форматированного текста (таблицы в текстовом файле):

verb666,Misha Verbitsky,+415314499922,42,11:00-16:00
ktvs421,Vasiliy Kotov,+415314499966,77a,00:00-06:00
dkldn89,Dmitry Kaledin,+415314499949,65b,22:00-00:00
vfurry1,Veniamin Furman,+415314499900,99,12:20-19:25
tpunk56,Tolik Punkoff,+415314499911,59,00:00-11:00

Исходная задача состояла в том, чтобы раскидать вывод awk в переменные bash, так, чтоб значение из соответствующей колонки таблицы попало в указанную переменную bash, и изначальный код был таким:

# ... цикл по строкам
for TMPSTRING in $(cat "testfile.txt")
do
    eval $(echo "$TMPSTRING"|awk -F "," '{print "LOGIN=" dq $1 dq; 
    print "FULLNAME=" dq $2 dq; print "PHONE=" dq $3 dq; print"ROOM=" dq $4 dq;
    print "WORKTIME=" dq $5 dq}' dq='"')

# ... делаем что-то с данными в переменных
done
# ...

Т.е. тут мы сначала получаем строчку текстового файла, передаем ее awk, awk, в свою очередь, возвращает строки:

...
LOGIN="vfurry1"
FULLNAME="Veniamin Furman"
PHONE="+415314499922"
ROOM="99"
WORKTIME="12:20-19:25"
...

Похоже на команду присваивания значений переменным. Да так оно и есть!
Далее, полученная строка передается команде eval.

Опасность eval

В чем опасность eval? Да в том, что эта команда преобразует любую переданную ей строку в команду или команды bash, и немедленно их выполняет. Любую, абсолютно любую строку.

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

verb666,Misha Verbitsky,+415314499922,42,11:00-16:00
ktvs421,Vasiliy Kotov,+415314499966,77a,00:00-06:00
dkldn89,Dmitry Kaledin,+415314499949,65b,22:00-00:00
vfurry1,Veniamin Furman `rm -rf ./testdir`,+415314499900,99,12:20-19:25
tpunk56,Tolik Punkoff,+415314499911,59,00:00-11:00

Тут, после Veniamin Furman вписана команда удаления директории ./testdir в текущем каталоге, а могло быть вписано и $HOME или даже /.

Строки, взятые в двойные кавычки, интерпретируются bash’ем не только, как строки с чисто тестовыми данными. В двойных кавычках можно и нужно сделать подстановку, если это возможно. А символы `` (обратные кавычки) значат, что нужно выполнить команду внктри них, а потом подставить результат в то место, где указано выражение в обратных кавычках.

Таким образом, команда

FULLNAME="Veniamin Furman `rm -rf ./testdir`"
выполнится так:
1. В переменную записывается строка Veniamin Furman
2. Выполняется команда rm -rf ./testdir
3. Вывод команды дописывается в переменную

Соответственно, каталог ./testdir в процессе будет удален.

Общие рекомендации

Всегда держите в голове, что данные внутри eval$() будут исполнены bash’ем, как код (команды оболочки).

Исправление

1. Заменить двойные кавычки (") на одинарные ('). Строки в одинарных кавычках будут интерпретированы просто как текстовые последовательности, и без всякого выполнения и изменения записаны в переменные, так как есть:

eval $(echo "$TMPSTRING"|awk -F "," '{print "LOGIN=" sq $1 sq;
print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq; print"ROOM=" sq $4 sq;
print "WORKTIME=" sq $5 sq}' sq="'")

Вывод:

vfurry1 Veniamin Furman `rm -rf ./testdir` +415314499900 99 12:20-19:25

2. Но и это еще не конец, злоумышленник может обмануть и одинарные кавычки, обернув свою команду в …одинарные кавычки:

vfurry1,Veniamin Furman '`rm -rf ./testdir`',+415314499900,99,12:20-19:25

Тогда выполнение будет происходить так. Когда очередь дойдет до FULLNAME awk добросовестно выведет:

FULLNAME='Veniamin Furman '`rm -rf ./testdir`'

eval же это также добросовестно интерпретирует. В $FULLNAME будет записано Veniamin Furman, далее закроется одинарная кавычка, т.е. больше ничего в переменную писаться не будет, а далее выполнится rm -rf ./testdir.

Решение таково — удалить или заменить на что-либо безобидное, одинарные кавычки из входной строки перед передачей ее awk. Можно удалить sed‘ом:

sed -e 's~'\''~~g'

Код целиком:

eval $(echo "$TMPSTRING"|sed -e 's~'\''~~g'|awk -F "," '{print "LOGIN=" sq $1 sq;
print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq; print"ROOM=" sq $4 sq;
print "WORKTIME=" sq $5 sq}' sq="'")

Вывод зловредной сткроки будет:

vfurry1 Veniamin Furman `rm -rf ./testdir` +415314499900 99 12:20-19:25

Тестовый пример

Файлы:

maketestdir — скрипт для создания тестовых каталогов.

testfile.txt — Тестовый файл. Ошибку безопасности можно обойти, заменив двойные кавычки одинарными в коде.
testfile2.txt — Замена двойных кавычек одинарными не поможет. Необходимо дополнительно удалять одинарные кавычки во входной строке.

wrongcode — файл без исправлений кода (в коде используются двойные кавычки)
wrongcode2 — только первое исправление (в коде используются одинарные кавычки)
goodcode — окончательно исправленный код.

Вызов скриптов: <скрипт> <тестовый файл>

Например:

wrongcode testfile2.txt

Пример на GitHub

Отключение всплывающих уведомлений в Firefox.

А заодно и другой лишней ерунды вроде доступа к микрофону или запроса геолокации.

1. Заходим в Инструменты —> Настройки
2. Переходим на вкладку «Приватность и защита»
Или сразу пишем в строке адреса: about:preferences#privacy
3. Мотаем вниз до раздела Разрешения:

4. Жмем кнопку «Параметры» напротив уведомлений, очищаем список, если в нем что-то есть, и ставим галочку Блокировать новые запросы на отправку вам уведомлений

5. Жмем Сохранить изменения
6. ФАНФАРЫ!

Таким же образом можно поступить с местоположением, камерой и микрофоном.

Записать вывод awk в несколько переменных bash

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

Преамбула

Предположим, у нас есть некоторая таблица в виде файла CSV с набором полей, например таких Login,FullName,Phone,Room,WorkTime и разделителем полей , (запятая):

verb666,Misha Verbitsky,+415314499922,42,11:00-16:00
ktvs421,Vasiliy Kotov,+415314499966,77a,00:00-06:00
dkldn89,Dmitry Kaledin,+415314499949,65b,22:00-00:00
vfurry1,Veniamin Furman,+415314499900,99,12:20-19:25
tpunk56,Tolik Punkoff,+415314499911,59,00:00-11:00

Нужно вытащить из нее некоторые данные, и далее как-либо обработать. Вытащить данные можно с помощью awk, используя оператор print, но возникает вопрос, как передать данные обратно в bash.

Предположим, что заголовок удален, в файле остались только данные.

awk и eval

В bash есть встроенная команда eval, преобразующая переданную ей строку в команду или набор команд оболочки, и запускающая ее на выполнение. Этим и воспользуемся.

1. Организуем цикл, в котором будем производить обработку данных:

IFS_=$IFS
IFS=$'\n'
for TMPSTRING in $(cat "demotable.txt")
do
#тут будет код
done
IFS=$IFS_

Перед циклом я подправил переменную $IFS содержащую глобальные разделители, в нее, в частности, «смотрят» операторы циклов, чтобы определить, где начинается следующий элемент. По умолчанию переменная $IFS содержит пробел, табуляцию и перевод строки, но поскольку у нас есть данные с пробелом, то это не подходит, цикл будет работать неверно. Потому сохраняем старое значение во временную переменную, устанавливаем новое значение в перевод строки (\n). После цикла возвращаем значение на место.
В цикле организуем разбор данных:

echo "$TMPSTRING"|awk -F "," '{print "LOGIN=" $1; print "FULLNAME=" $2
print "PHONE=" $3; print "ROOM=" $4; print "WORKTIME=" $5 }'

Если запустить скрипт сейчас, то он выведет следующее:

LOGIN=verb666
FULLNAME=Misha Verbitsky
PHONE=+415314499922
ROOM=42
WORKTIME=11:00-16:00

Т.е. уже похоже на присваивание значений переменным bash, но есть проблема. Если мы сейчас скормим вывод awk eval‘у, то получим ошибку, например такую:

./awk2vars01: line 8: Verbitsky: command not found

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

awk print и вывод кавычки

Кавычки для оператора print awk являются служебными символами, в двойные кавычки берутся строковые литералы, т.е. те строки, которые нужно вывести без изменений, как например, "LOGIN=" в коде выше, а в одинарные — вся программа awk. Экранирование (\" или \') в операторе print приведет к ошибке.

Решение — завести внутреннюю переменную awk, содержащую кавычку, и печатать ее в нужном месте:

echo "$TMPSTRING"|sed -e 's~'\''~~g'|awk -F "," '{print "LOGIN=" sq $1 sq; 
    print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq; print"ROOM=" sq $4 sq;
    print "WORKTIME=" sq $5 sq}' sq="'"

Поскольку данные строки далее будут переданы в eval и обработаны как команды оболочки, то необходимо позаботиться о безопасности, и использовать только одинарные кавычки, а также удалять одинарные кавычки из входных строк, при передаче их awk:

Об опасности использования eval в bash-скриптах. Копия

Вывод:

LOGIN='verb666'
FULLNAME='Misha Verbitsky'
PHONE='+415314499922'
ROOM='42'

Теперь можно обернуть все это в eval, чтобы раскидать результат работы awk по переменным.

eval $(echo "$TMPSTRING"|sed -e 's~'\''~~g'|awk -F "," '{print "LOGIN=" sq $1 sq; 
    print "FULLNAME=" sq $2 sq; print "PHONE=" sq $3 sq;
    print"ROOM=" sq $4 sq;
    print "WORKTIME=" sq $5 sq}' sq="'")

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

echo "Login: $LOGIN"
echo "Full name: $FULLNAME"
echo "Phone: $PHONE"
echo "Room: $ROOM"
echo "Work time: $WORKTIME"

Вывод:

Login: verb666
Full name: Misha Verbitsky
Phone: +415314499922
Room: 42
Work time: 11:00-16:00
...

Скрипты полностью можно посмотреть на GitHub

Немного об оптимизации

Получение строки с определенным номером из файла в Linux

С помощью sed

sed -n 5p /path/to/file

Получить 5 строку из текстового файла с путем /path/to/file

С помощью awk

awk 'NR == 5' /path/to/file

На мой взгляд sed‘ом несколько проще, потому что не нужны лишние движения для подстановки переменных shell/bash-скрипта:

TEXTFILE="/etc/group"
STRNO=5

TMPSTRING=`sed -n "$STRNO"p "$TEXTFILE"`

Подключение Riseup VPN в Windows XP

Приходится кое-где сидеть на старой машине с XP (хорошо, не с 2000), а безопасно посидеть в интернете хочется. Так что от склероза, и мало ли кому надо будет. Аналогичным образом можно подключить на XP и VPN от calyx.net

Подготовка

1. Необходимо установить OpenVPN для Windows XP:

Скачать с официального сайта:

Версия x86 (32-разрядная)
Версия x64 (64-разрядная)

Копии, на случай, если с официального сайта пропадет:

Версия x86 (32-разрядная)
Версия x64 (64-разрядная)

2. Далее, качаем конфиги:

Для Riseup:
C Mega.nz
C Google.Drive

Для Calyx:
C Mega.nz
C Google.Drive

3. Конфиги необходимо распаковать в каталог для файлов конфигурации OpenVPN (C:\Program Files\OpenVPN\config)

Получение ключей и сертификатов

Получение ключей для VPN сделано обычным BAT-файлом, без проверок fingerprint’а сертификата провайдера, а в wget, которым все и выкачивается, указан ключ --no-check-certificate, так что все это дело не сильно секурно.

1. Скачиваем архив с GitHub
На всякий случай копия на Google.Drive и Mega.nz

2. Распаковываем архив в отдельный каталог, пусть будет C:\GetKeys
3. Запускаем командную строку (Пуск —> Выполнить и вводим cmd)
4. Переходим в каталог:

С:
cd \GetKeys

5. Запускаем получение ключей.
Для Riseup:

getkeys.bat riseup.net

Для Calyx, соответственно:

getkeys.bat calyx.net

После того, как батник отработает, в C:\GetKeys образуется подкаталог data с подкаталогами провайдеров. Из C:\GetKeys\data\riseup.net надо скопировать в C:\Program Files\OpenVPN\config файлы cacert.pem и openvpn.pem для Riseup VPN, а для Calyx из C:\GetKeys\data\calyx.net те же файлы, переименовав их, соответственно как cacert_calyx.pem и openvpn_calyx.pem. Или можете отредактировать конфиг calyx-net.ovpn и задать там любые желательные имена.

Теперь можно запускать OpenVPN GUI, выбирать, щелкнув по иконке в трее, нужный конфиг, и соединяться.

Проверка

До соединения:

Riseup:


Calyx

Получение ключей и сертификатов для Riseup VPN и calyx.net в Windows.

Без использования клиента Riseup или Bitmask для Calyx.

Преамбула

Наскриптил тут утилитку, которая выкачивает ключи для Riseup VPN копия, а заодно уж и для calyx копия под Windows. Изначально писалось для XP, т.к. оказалось, что официальный клиент Riseup в XP не работает, а Calyx вообще использует Bitmask, которого для винды не предвидится.

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

Так что получилась такая утилита, чтобы подключаться к Riseup и Calyx не качая официального клиента. Пусть уж будет.

Порядок действий

1. Ставим OpenVPN
2. Качаем виндоконфиги:
Для Riseup:
C Mega.nz
C Google.Drive
Для Calyx:
C Mega.nz
C Google.Drive
3. Распаковываем конфиги в каталог конфигов OpenVPN (Обычно C:\Users\<имя_пользователя>\OpenVPN\config\).
4. Качаем утилиту отсюда и распаковываем в отдельный каталог. Она портабельная, никакой установки не надо.
5. Запускаем.
6. Выбираем в выпадающем списке провайдера.
7. Жмем на кнопку «Получить ключ пользователя» Если данных и сертификата провайдера нет, то программа сначала получит данные провайдера, а потом пользователя. Если данные провайдера есть, то обновится только пользовательский ключ.
Если в дальнейшем надо будет принудительно обновить данные провайдера, жмем кнопку «Обновить данные провайдера«

Примечание: Если надо добавить нового провайдера, то дописываем адрес без http://, https:// и www в файл providers.txt в подкаталоге data, по адресу на строку.

Должно получиться как на скриншоте:

8. Жмем кнопку «Сохранить ключи…» и сохраняем их в каталог с конфигами OpenVPN. Имена ключей для Riseup оставляем без изменений, а для Calyx сохраняем как cacert_calyx.pem и openvpn_calyx.pem. Или можете отредактировать конфиг calyx-net.ovpn и задать там любые желательные имена.

Репозиторий на GitHub

Полезные фишки в BAT/CMD файлах.

Выход из BAT-файла

exit /B

Ключ /B обязателен! Если использовать просто exit — закроется командный интерпретатор.

Проверка, пустая ли переменная

Можно использовать для проверки параметров в командной строке.

if "%~1"=="" (
	echo Use %0 ^<address^>
	echo Address must be without http://, https:// or www
	echo e.g. riseup.net
	pause
	exit /B
)

Запись вывода программы в переменную

Аналог VAR=`program` в bash.

for /f %%i in ('program') do set "API_URI=%%i"& goto f1
:f1

Пример:

for /f %%i in ('bin\jq .api_uri %WORKDIR%/provider.json') do set "API_URI=%%i"& goto f1
:f1

Источник

SetLocal и расширенная обработка команд

Статья на Киберфоруме Копия в PDF

Экранирование спецсимволов

В BAT/CMD файле некоторые символы (перенаправления > и <, конвейера |, символ &, указание переменной %) считаются специальными. Если символ должен быть включен в команду как символ, могут быть глюки. Перед символом нужно указать символ экранирования: ^ (крышку).

Пример:

echo Use batfile.bat ^<address^>

(выведет на экран Use batfile.bat <address>)

Источник Копия в PDF Там есть и другие полезные штуковины.

Удаление определенного символа из строки.

Например, надо удалить из переменной %VAR% все кавычки:

set VAR=%VAR:"=%

Источник Копия в PDF В источнике есть и другие примеры работы со строками.

C#, HttpWebRequest: использование самоподписанного (self-signed) сертификата.

Преамбула

Если попытаться соединиться с HTTPS-сервером, имеющим самоподписанный сертификат, то HttpWebRequest сгенерирует исключение WebException Базовое соединение закрыто: Не удалось установить доверительные отношения для защищенного канала SSL/TLS. (The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.). Оно и понятно, HttpWebRequest не смог проверить сертификат узла и закономерно нас послал.

Решение заключается в переопределении глобального обработчика System.Net.ServicePointManager.ServerCertificateValidationCallback.

Внимание! Изменение ServerCertificateValidationCallback повлияет на все соединения в программе, пока обработчик не будет установлен по умолчанию, надо не забывать это, и возвращать обработчик к стандартному значению, после того, как работа с сервером, имеющим самоподписанный сертификат, завершена.

Предположим, есть класс-обертка над HttpWebRequest, надо его дополнить так, чтоб можно было работать с серверами с self-signed сертификатами.

Игнорирование неправильных сертификатов

Самый простой способ, но не самый безопасный. ServerCertificateValidationCallback передается делегат, который всегда возвращает true. Добавляем в класс функцию:

public void EnableIgnoreCertError()
{
    ServicePointManager.ServerCertificateValidationCallback =
        delegate { return true; };
}

И функцию, которая возвращает ServerCertificateValidationCallback к значению по умолчанию:

public void DisableIgnoreCertError()
{
    ServicePointManager.ServerCertificateValidationCallback = 
        null;
}

Начали работать с сервером — вызвали первую, закончили — вызвали вторую.

Самостоятельная проверка сертификата

Немного более сложный, но более безопасный способ. Необходимо заранее либо иметь сам сертификат, либо знать его хэш. В следующем примере будем проверять как раз хэш SHA1 сертификата.
1. Подключим в References’ах System.Security.Cryptography.
2. Подключим необходимые пространства имен:

using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

3. Заведем в классе поле, куда из вызывающей программы будем передавать заранее известный хэш сертификата:

public string CertHashString { get; set; }

4. Заведем две функции, для включения и выключения самостоятельной проверки сертификата:

public void EnableValidateCert()
{
    ServicePointManager.ServerCertificateValidationCallback =
        ValidateCert;
    
}

public void DisableValidateCert()
{
    ServicePointManager.ServerCertificateValidationCallback =
        null;
}

В первой функции вместо делегата, возвращающего true, указываем нашу функцию проверки.

Функция проверки сертификата

Функция проверки сертификата обязательно должна иметь следующий заголовок:
bool FunctionName (object, X509Certificate, X509Chain, SslPolicyErrors), т.е., например:

private bool ValidateCert(object sender, X509Certificate cert,
    X509Chain chain, SslPolicyErrors sslPolicyErrors)

В функции:
1. Проверяем наличие ошибок SSL, если их нет, значит сертификат уже был опознан:

if (sslPolicyErrors == SslPolicyErrors.None)
{
    return true;
}

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

if (string.IsNullOrEmpty(CertHashString))
{
    return false;
}

3. Получаем хэш сертификата сервера в виде строки:

string hashstring = cert.GetCertHashString();

4. Если хэши полученного и известного сертификата совпадают — все ок, возвращаем true, иначе false.

if (hashstring == CertHashString)
{
    return true;
}

return false;

Функция целиком:

private bool ValidateCert(object sender, X509Certificate cert,
    X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    //все и так хорошо
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        return true;
    }

    //не передан хэш сертификата
    //значит и проверять нечего
    if (string.IsNullOrEmpty(CertHashString))
    {
        return false;
    }

    //получаем хэш сертификата сервера в виде строки
    string hashstring = cert.GetCertHashString();

    //если хэши полученного и известного 
    //сертификата совпадают - все ок.
    if (hashstring == CertHashString)
    {
        return true;
    }

    return false;
}

Класс целиком на PasteBin
На GitHub

Источник

C#, получение хэша (fingerprint) сертификата X509.

Преамбула

Понадобилось проверить сертификат X509 на целостность и подлинность. Поставщик сертификата отдельно передает его fingerprint и алгоритм хэширования по которому тот вычисляется.

Судя по документации от MS, получить fingerprint, он же хэш, можно функцией GetCertHashString() (или GetCertHash() в виде массива байт) класса X509Certificate2, однако, в .NET 2.0 отсутствует перегрузка функции GetCertHashString(HashAlgorithmName), которая позволяет выбрать алгоритм хеширования (GetCertHashString() возвращает хэш SHA1). Потому сделаем руками, благо ничего сложного в этом нет.

Получение fingerprint

1. Подключаем в References System.Security.Cryptography
2. Подключаем необходимые пространства имен в классе:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

3. Загружать сертификат будем из файла, потому проверяем его наличие:

if (!File.Exists(CertFile))
{
    Console.WriteLine("File not found.");
    return null;
}

4. Загружаем сертификат (создаем экземпляр класса X509Certificate2). Если вдруг файл сертификата поврежден, то конструктор X509Certificate2 вызовет Exception Требуемый объект не найден. Поэтому конструктор надо обернуть в try...catch:

try
{
    X509 = new X509Certificate2(CertFile);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    return null;
}

5. Получаем загруженный сертификат в виде массива байт:

byte[] cert = X509.GetRawCertData();

6. Создаем объект HashAlgorithm, который и займется вычислением хэша/fingerprint’а. Я завел в функции входную переменную AlgName, куда записывается строка с названием алгоритма, что бы иметь возможность выбора алгоритма хэширования если что:

HashAlgorithm alg = null;
switch (AlgName.ToUpperInvariant())
{
    case "MD5": alg = MD5.Create(); break;
    case "SHA1": alg = SHA1.Create(); break;
    case "SHA256": alg = SHA256.Create(); break;
    case "SHA384": alg = SHA384.Create(); break;
    case "SHA512": alg = SHA512.Create(); break;
    default:
        {
            Console.WriteLine("Unknow algorithm.");
            return null;
        }
}

7. Получаем хэш в виде массива байт:

byte[] hash = alg.ComputeHash(cert);

8. Преобразуем массив байт в строку шестнадцатеричных чисел:

string hex = BitConverter.ToString(hash).ToLowerInvariant().
    Replace("-", "");

ToLowerInvariant() преобразует буквы в строке, полученной BitConverter в строчные, а Replace("-", "") удаляет разделители (-) между значениями байтов, которые BitConverter вставляет в строку.

Код функции целиком
Тестовый пример на GitHub

C#, парсинг и прочая работа с JSON. В том числе и в .NET 2.0

Преамбула

Понадобилось достать несколько значений из JSON-файла, искал, чем это сделать и нашел просто офигенную библиотеку Json.NET. Это не просто парсер, это целый набор инструментов: парсер, сериализаторы, десериализаторы, конвертеры. Кроме банального вытаскивания значений, можно формировать JSON, конвертировать его в XML и XML в JSON, обращаться к объектам JSON через запрос SelectToken или LINQ. А можно и просто вытащить нужное значение, обращаясь к полям JSON, используя встроенные функции объектов.

Несомненный плюс библиотеки в том, что даже в последней версии она поддерживает .NET Framework 2.0 Правда нельзя будет использовать синтаксис LINQ, но и без него инструментов хватает.

Простой парсинг и извлечение значений

С помощью JObject и JToken

1. Подключаем библиотеку для своего Framework’а через References и в исходнике:

using Newtonsoft.Json.Linq;

2. Загружаем JSON в переменную, например из файла.

3. Парсим JSON в JObject:

JObject JSONObj = JObject.Parse(JSONBuf);

JObject.Parse может вызвать exception, если есть синтаксические ошибки в JSON, так что лучше поместить его в try...catch:

public static JObject ParseJSON(string JSONBuf)
{
    JObject JSONObj = null;
    try
    {
        JSONObj = JObject.Parse(JSONBuf);
    }
    catch (Exception ex)
    {
        Console.WriteLine("ERROR: " + ex.Message);
        return null;
    }
    return JSONObj;
}

Пример реакции на ошибку синтаксиса:

Invalid character after parsing property name. Expected ':' but got: {. Path description.languages', line 15, position 9.

4. Далее, можно вытащить значение поля JSON в объект JToken помощью функции JSONObj.GetValue(FieldName);, если поле со значением находится сразу в корне JSON, как, например, api_uri в этом примере, то JToken можно сразу преобразовать в строку с помощью функции .ToString:

JToken tok = JSONObj.GetValue("api_uri");
tok.ToString();

Однако, если поле отсутствует, то это вызовет exception:

В экземпляре объекта не задана ссылка на объект.

Этого можно избежать, отловив исключение в try...catch, либо проверив наличие поля функцией JObject.ContainsKey("имя_поля") перед вызовом GetValue, либо воспользовавшись функцией TryGetValue вместо GetValue

Примеры кода на PasteBin
Тестовый проект

Если же нужное значение находится где-то глубоко в недрах JSON, то необходимо воспользоваться функцией JObject.SelectToken() Пример в официальной документации Копия

Что дальше?

Как я уже говорил, инструмент очень мощный, так что лучше начать с изучения официальной документации

Скачать

Json.NET с официального сайта
Копия библиотеки v 120r3

OpenVPN для Windows XP.

Последняя поддерживающая Windows XP версия OpenVPN это 2.3.18.

Please note that OpenVPN 2.4 installers will not work on Windows XP. The last OpenVPN version that supports Windows XP is 2.3.18, which is downloadable as 32-bit and 64-bit versions.

Скачать с официального сайта

Версия x86 (32-разрядная)
Версия x64 (64-разрядная)

Копии

Версия x86 (32-разрядная)
Версия x64 (64-разрядная)

C#, поиск файла по маске.

Или небольшая заметка про странный глюк (на самом деле фичу) MS и кривофикс к ней.

Словил странный баг при поиске файлов по маске, функцией Directory.GetFiles();

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

Т.е. на *.ph оно найдет только файлы с расширением ph, а на *.a — только файлы с расширением a

Этот баг распространяется только на последнее расширение. Если в имени файла есть конструкция типа *.tmp.php, например admin.tmp.php, то при задании маски *.php расширение tmp будет, слава Ктулху, проигнорировано.

Оказалось, что это не баг, а фича, и об этом прямо написано в MSDN:

Если указанное расширение имеет длину ровно три символа, метод возвращает файлы с расширениями, которые начинаются с указанного расширения. Например, «*. xls» возвращает оба значения: «Book.xls» и «Book.xlsx».

Не знаю, как создатели DOS смогли заговнять и испортить самую простую операцию, которая работала у них, как минимум, с 1989 года, но факт. Заговняли.

В общем, я теряюсь в догадках.

Кривофикс

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

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

private string GetExtension(string FileName)
{
    FileName = FileName.Replace('*', '_');
    FileName = FileName.Replace('?', '-');
    FileInfo fi = new FileInfo(FileName);
    return fi.Extension;
}

Замены Replace(...) тут для того, чтобы класс FileInfo не выпал в Exeption, если ему подать что-то вида *.html, т.к. FileInfo не принимает имен с недопустимыми символами, к которым относятся и маски подстановки. В определении расширений класс FileInfo такого глюка не имеет, и отличает file.html от file.htm. И тоже не признает двойные расширения, учитывая только последнее.

2. В функции, где будем вызывать поиск файлов, вызываем, собственно, функцию поиска:

string[] files = Directory.GetFiles(Path, Mask, SearchOptions);

3. Получаем расширение маски:

string MaskExt = GetExtension(Mask);

4. Далее обрабатываем выходной массив. Например, тут я добавлял его в List<string> с именем FoundFiles. Мне надо было искать файлы по маскам, и нужно было, чтоб *.htm и, например, *.html различались.

Например, я просто сравнивал расширение от маски файла с расширением от имени файла, и если оно совпадало — добавлял в результирующий список FoundFiles:

string FileExt = GetExtension(filename);
if (FileExt == MaskExt)
{
    FoundFiles.Add(filename);
}

Или относительно полностью:

string MaskExt = GetExtension(Mask);
//[...]
string[] files = Directory.GetFiles(Path, Mask, SearchOptions);
foreach (string filename in files)
{
    string FileExt = GetExtension(filename);
    if (FileExt == MaskExt)
    {
        FoundCtr++;
        FoundFiles.Add(filename);
    }
}