amnezia-client/client/protocols/ikev2_vpn_protocol_windows.cpp
2025-01-28 23:59:50 +02:00

329 lines
11 KiB
C++

#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QThread>
#include <chrono>
#include "logger.h"
#include "ikev2_vpn_protocol_windows.h"
#include "utilities.h"
static Ikev2Protocol* self = nullptr;
static std::mutex rasDialFuncMutex;
extern "C" {
static void WINAPI RasDialFuncCallback(UINT unMsg,
RASCONNSTATE rasconnstate,
DWORD dwError );
}
Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) :
VpnProtocol(configuration, parent)
{
self = this;
readIkev2Configuration(configuration);
}
Ikev2Protocol::~Ikev2Protocol()
{
qDebug() << "IpsecProtocol::~IpsecProtocol()";
disconnect_vpn();
Ikev2Protocol::stop();
}
void Ikev2Protocol::stop()
{
setConnectionState(Vpn::ConnectionState::Disconnecting);
{
if (! disconnect_vpn() ){
qDebug()<<"We don't disconnect";
setConnectionState(Vpn::ConnectionState::Error);
}
else {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
}
}
void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE rasconnstate, DWORD dwError)
{
Q_UNUSED(unMsg);
qDebug()<<"Receive the new event "<<static_cast<int>(rasconnstate);
switch (rasconnstate)
{
case RASCS_OpenPort:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_PortOpened:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_ConnectDevice:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_DeviceConnected:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_AllDevicesConnected:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_Authenticate:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_AuthNotify:
//qDebug()<<__FUNCTION__ << __LINE__;
if (dwError != 0) {
qDebug() << "have error" << dwError;
setConnectionState(Vpn::ConnectionState::Disconnected);
} else {
qDebug() << "RASCS_AuthNotify but no error" << dwError;
}
break;
case RASCS_AuthRetry:
//qDebug()<<__FUNCTION__ << __LINE__;
setConnectionState(Vpn::ConnectionState::Preparing);
break;
case RASCS_AuthCallback:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthChangePassword:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthProject:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthLinkSpeed:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_AuthAck:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_ReAuthenticate:
//qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Authenticated:
//qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_PrepareForCallback:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_WaitForModemReset:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_WaitForCallback:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Projected:
qDebug()<<__FUNCTION__ << __LINE__;
break;
#if (WINVER >= 0x400)
case RASCS_StartAuthentication: // Windows 95 only
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_CallbackComplete: // Windows 95 only
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_LogonNetwork: // Windows 95 only
qDebug()<<__FUNCTION__ << __LINE__;
break;
#endif
case RASCS_SubEntryConnected:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_SubEntryDisconnected:
qDebug()<<__FUNCTION__ << __LINE__;
break;
//PAUSED STATES:
case RASCS_Interactive:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_RetryAuthentication:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_CallbackSetByCaller:
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_PasswordExpired:
setConnectionState(Vpn::ConnectionState::Error);
qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Connected: // = RASCS_DONE:
setConnectionState(Vpn::ConnectionState::Connected);
//qDebug()<<__FUNCTION__ << __LINE__;
break;
case RASCS_Disconnected:
setConnectionState(Vpn::ConnectionState::Disconnected);
//qDebug()<<__FUNCTION__ << __LINE__;
break;
default:
//qDebug()<<__FUNCTION__ << __LINE__;
break;
}
}
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
{
m_config = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject();
}
ErrorCode Ikev2Protocol::start()
{
QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8());
setConnectionState(Vpn::ConnectionState::Connecting);
QTemporaryFile * certFile = new QTemporaryFile;
certFile->setAutoRemove(false);
certFile->open();
QString m_filename = certFile->fileName();
certFile->write(cert);
certFile->close();
delete certFile;
{
auto certInstallProcess = IpcClient::CreatePrivilegedProcess();
if (!certInstallProcess) {
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
certInstallProcess->waitForSource();
if (!certInstallProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
certInstallProcess->setProgram(PermittedProcess::CertUtil);
QStringList arguments({"-f", "-importpfx", "-p", m_config[config_key::password].toString(),
QDir::toNativeSeparators(m_filename), "NoExport"
});
certInstallProcess->setArguments(arguments);
certInstallProcess->start();
}
// /*
{
if ( disconnect_vpn()){
qDebug()<<"VPN was disconnected";
}
if ( delete_vpn_connection (tunnelName())){
qDebug()<<"VPN was deleted";
}
}
{
{
if ( !create_new_vpn(tunnelName(), m_config[config_key::hostName].toString())){
qDebug() <<"Can't create the VPN connect";
}
}
}
{
QProcess adapterConfigProcess;
adapterConfigProcess.setProgram("powershell");
QString arguments = QString("-command \"Set-VpnConnectionIPsecConfiguration\" "
"-ConnectionName '%1' "
"-AuthenticationTransformConstants GCMAES128 "
"-CipherTransformConstants GCMAES128 "
"-EncryptionMethod AES256 "
"-IntegrityCheckMethod SHA256 "
"-PfsGroup PFS2048 "
"-DHGroup Group14 "
"-PassThru -Force\"")
.arg(tunnelName());
adapterConfigProcess.setNativeArguments(arguments);
adapterConfigProcess.start();
adapterConfigProcess.waitForFinished(5000);
}
//*/
{
if (!connect_to_vpn(tunnelName())) {
qDebug()<<"We can't connect to VPN";
}
}
//setConnectionState(Connecting);
return ErrorCode::NoError;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::create_new_vpn(const QString & vpn_name,
const QString & serv_addr){
if ( RasValidateEntryName(nullptr, vpn_name.toStdWString().c_str()) != ERROR_SUCCESS)
return false;
DWORD size = 0;
::RasGetEntryProperties(nullptr, L"", nullptr, &size, nullptr, nullptr);
LPRASENTRY pras = static_cast<LPRASENTRY>(malloc(size));
memset(pras, 0, size);
pras->dwSize = size;
pras->dwType = RASET_Vpn;
pras->dwRedialCount = 1;
pras->dwRedialPause = 60;
pras->dwfNetProtocols = RASNP_Ip|RASNP_Ipv6;
pras->dwEncryptionType = ET_RequireMax;
wcscpy_s(pras->szLocalPhoneNumber, serv_addr.toStdWString().c_str());
wcscpy_s(pras->szDeviceType, RASDT_Vpn);
pras->dwfOptions = RASEO_RemoteDefaultGateway;
pras->dwfOptions |= RASEO_RequireDataEncryption;
pras->dwfOptions2 |= RASEO2_RequireMachineCertificates;
pras->dwVpnStrategy = VS_Ikev2Only;
const auto nRet = ::RasSetEntryProperties(nullptr, vpn_name.toStdWString().c_str(), pras, pras->dwSize, NULL, 0);
free(pras);
if (nRet == ERROR_SUCCESS)
return true;
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){
if ( RasDeleteEntry(nullptr, vpn_name.toStdWString().c_str()) == ERROR_SUCCESS){
return true;
}
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
RASDIALPARAMS RasDialParams;
memset(&RasDialParams, 0x0, sizeof(RASDIALPARAMS));
RasDialParams.dwSize = sizeof(RASDIALPARAMS);
wcscpy_s(RasDialParams.szEntryName, vpn_name.toStdWString().c_str());
auto ret = RasDial(NULL, NULL, &RasDialParams, 0,
&RasDialFuncCallback,
&hRasConn);
if (ret == ERROR_SUCCESS){
return true;
}
return false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::disconnect_vpn(){
if ( hRasConn != nullptr ){
if ( RasHangUp(hRasConn) != ERROR_SUCCESS)
return false;
}
QThread::msleep(3000);
return true;
}
void WINAPI RasDialFuncCallback(UINT unMsg,
RASCONNSTATE rasconnstate,
DWORD dwError ){
std::lock_guard<std::mutex> guard(rasDialFuncMutex);
if (self) {
self->newConnectionStateEventReceived(unMsg, rasconnstate, dwError);
}
}