Недавно началось мое сотрудничество с одним книжным издательством. Мне по наследству выпало поддерживать ихний сайт. Я для них на Delphi написал программу для добавления отсканированных листов книг на сайт. Для передачи файлов на сайт использовался старый добрый ftp и все было замечательно. Святая простота! Когда я установил в издательстве программу, в тот же день на сайт пролезли веселые вирусы и провайдер отключил сайт. Стало понятным, что использовать ftp нельзя ведь в нем данные аккаунта передаются в открытом виде. Пришлось искать другие варианты.
Выбор пал на протокол sftp так как к сайту был доступ по ssh.
Я в своих Delphi проектах для работы с сетью использую библиотеку Synapse. Не могу толком сказать, почему именно на нее пал мой выбор. Просто однажды попробовал. Что-то заработало и после этого тянется эта дружба.
В примерах библиотеки Synapse есть программа TestSftp. Первым делом собрал этот проект для проверки. Сначала проект даже не собрался. Дело в том, что для шифрование он использует библиотеку CryptLib. Пришлось раздобыть файл cryptlib.pas для проекта, ну и динамическую библиотеку cl32.dll. Проект собрался, но при попытке подключении меня ожидала неудача. Выскакивала ошибка
Synapse TCP/IP Socket error 10091:
Собравшись с силами я решил искать решение проблемы в дебрях исходников библиотеки CryptLib. Благо она оупенсорсная.
Собрав Debug версию библиотеки я при подключение получил сообщение с более глубокомысленным текстом:
Synapse TCP/IP Socket error 10091: Invalid PAM authentication request packet
Прошерстив библиотеку я обнаружил что это сообщение возникает в функции pamAuthenticate в файле ssh2_authc.c.
Библиотека CryptLib имеет классную фичу. Отладочная версия выводит в окно Output протокол обмена. Вот кусок, на котором происходит залипание
Read SSH_MSG_USERAUTH_INFO_REQUEST (60) packet, length 59.
0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................
0016: 00 00 00 26 50 61 73 73 77 6F 72 64 20 66 6F 72 ...&Password for
0048: 6F 63 61 6C 6C 2E 6E 65 74 3A 00 ocall.net:.
Wrote SSH_MSG_USERAUTH_INFO_RESPONSE (61) packet, length 20.
0000: 00 00 00 01 00 00 00 xx xx xx xx xx xx xx xx xx ........Password
0016: xx xx xx xx
Read SSH_MSG_USERAUTH_INFO_REQUEST (60) packet, length 16.
0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
А вот выдержка из протокола программы psftp входящей в состав утилиты Putty, в котором соединение происходило нормально
Incoming packet #0x5, type 60 / 0x3c (SSH2_MSG_USERAUTH_INFO_REQUEST)
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................
00000010 00 00 00 26 50 61 73 73 77 6f 72 64 20 66 6f 72 ...&Password for
00000030 6f 63 61 6c 6c 2e 6e 65 74 3a 00 ocall.net:.
Outgoing packet #0x6, type 61 / 0x3d (SSH2_MSG_USERAUTH_INFO_RESPONSE)
00000000 00 00 00 01 XX XX XX XX XX XX XX XX XX XX XX XX ....XXXXXXXXXXXX
00000010 XX XX XX XX XXXX C...
Incoming packet #0x6, type 60 / 0x3c (SSH2_MSG_USERAUTH_INFO_REQUEST)
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Outgoing packet #0x8, type 61 / 0x3d (SSH2_MSG_USERAUTH_INFO_RESPONSE)
00000000 00 00 00 00
Получается, что после того как сервер запросил и получил пароль он отправляет еще один пустой пакет запроса SSH2_MSG_USERAUTH_INFO_REQUEST на который psftp отсылала пустой пакет ответа SSH2_MSG_USERAUTH_INFO_RESPONSE, а библиотека CryptLib падала.
Я решил пропатчить библиотеку, чтоб она вела себя аналогично программе psftp.
В файле ssh2_authc.c я сделал следующее изменение в функции pamAuthenticate:
заменил блок
if( noPrompts <= 0 || noPrompts > 4 )
{
/* Requesting zero or more than a small number of prompts is
suspicious */
status = CRYPT_ERROR_BADDATA;
}
}
на
if( noPrompts < 0 || noPrompts > 4 )
{
/* Requesting zero or more than a small number of prompts is
suspicious */
status = CRYPT_ERROR_BADDATA;
}
else if(noPrompts == 0)
{
//Отправим назад пустой пакет
status = openPacketStreamSSH( &stream, sessionInfoPtr,
SSH_MSG_USERAUTH_INFO_RESPONSE );
if( cryptStatusError( status ) )
return( status );
writeUint32( &stream, 0);
status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
return status;
}
После этого авторизация стала проходить. Возникла другая ошибка, но тут уже дело было не в CryptLib.
Результат патча для CryptLib.