#include "FileZilla.h" #include "../Options.h" #include "settingsdialog.h" #include "optionspage.h" #include "optionspage_connection_sftp.h" #include "../filezillaapp.h" #include "../inputdialog.h" #include <wx/tokenzr.h> BEGIN_EVENT_TABLE(COptionsPageConnectionSFTP, COptionsPage) EVT_END_PROCESS(wxID_ANY, COptionsPageConnectionSFTP::OnEndProcess) EVT_BUTTON(XRCID("ID_ADDKEY"), COptionsPageConnectionSFTP::OnAdd) EVT_BUTTON(XRCID("ID_REMOVEKEY"), COptionsPageConnectionSFTP::OnRemove) EVT_LIST_ITEM_SELECTED(wxID_ANY, COptionsPageConnectionSFTP::OnSelChanged) EVT_LIST_ITEM_DESELECTED(wxID_ANY, COptionsPageConnectionSFTP::OnSelChanged) END_EVENT_TABLE() COptionsPageConnectionSFTP::COptionsPageConnectionSFTP() { m_pProcess = 0; m_initialized = 0; } COptionsPageConnectionSFTP::~COptionsPageConnectionSFTP() { if (m_pProcess) { m_pProcess->CloseOutput(); m_pProcess->Detach(); m_pProcess = 0; } } bool COptionsPageConnectionSFTP::LoadPage() { wxListCtrl* pKeys = XRCCTRL(*this, "ID_KEYS", wxListCtrl); if (!pKeys) return false; pKeys->InsertColumn(0, _("Filename"), wxLIST_FORMAT_LEFT, 150); pKeys->InsertColumn(1, _("Comment"), wxLIST_FORMAT_LEFT, 100); pKeys->InsertColumn(2, _("Data"), wxLIST_FORMAT_LEFT, 350); // Generic wxListCtrl has gross minsize wxSize size = pKeys->GetMinSize(); size.x = 1; pKeys->SetMinSize(size); wxString keyFiles = m_pOptions->GetOption(OPTION_SFTP_KEYFILES); wxStringTokenizer tokens(keyFiles, _T("\n"), wxTOKEN_DEFAULT); while (tokens.HasMoreTokens()) AddKey(tokens.GetNextToken(), true); bool failure = false; SetCtrlState(); return !failure; } bool COptionsPageConnectionSFTP::SavePage() { // Don't save keys on process error if (!m_initialized || m_pProcess) { wxString keyFiles; wxListCtrl* pKeys = XRCCTRL(*this, "ID_KEYS", wxListCtrl); for (int i = 0; i < pKeys->GetItemCount(); i++) { if (keyFiles != _T("")) keyFiles += _T("\n"); keyFiles += pKeys->GetItemText(i); } m_pOptions->SetOption(OPTION_SFTP_KEYFILES, keyFiles); } if (m_pProcess) { m_pProcess->CloseOutput(); m_pProcess->Detach(); m_pProcess = 0; } return true; } bool COptionsPageConnectionSFTP::LoadProcess() { if (m_initialized) return m_pProcess != 0; m_initialized = true; wxString executable = m_pOptions->GetOption(OPTION_FZSFTP_EXECUTABLE); int pos = executable.Find(wxFileName::GetPathSeparator(), true); if (pos == -1) { wxMessageBox(_("fzputtygen could not be started.\nPlease make sure this executable exists in the same directory as the main FileZilla executable."), _("Error starting program"), wxICON_EXCLAMATION); return false; } else { executable = executable.Left(pos + 1) + _T("fzputtygen"); #ifdef __WXMSW__ executable += _T(".exe"); #endif // Restore quotes if (executable[0] == '"') executable += '"'; } m_pProcess = new wxProcess(this); m_pProcess->Redirect(); if (!wxExecute(executable, wxEXEC_ASYNC, m_pProcess)) { delete m_pProcess; m_pProcess = 0; wxMessageBox(_("fzputtygen could not be started.\nPlease make sure this executable exists in the same directory as the main FileZilla executable."), _("Error starting program"), wxICON_EXCLAMATION); return false; } return true; } bool COptionsPageConnectionSFTP::Send(const wxString& cmd) { if (!m_pProcess) return false; const wxWX2MBbuf buf = (cmd + _T("\n")).mb_str(); const size_t len = strlen(buf); wxOutputStream* stream = m_pProcess->GetOutputStream(); stream->Write((const char*)buf, len); if (stream->GetLastError() != wxSTREAM_NO_ERROR || stream->LastWrite() != len) { wxMessageBox(_("Could not send command to fzputtygen."), _("Command failed"), wxICON_EXCLAMATION); return false; } return true; } enum COptionsPageConnectionSFTP::ReplyCode COptionsPageConnectionSFTP::GetReply(wxString& reply) { if (!m_pProcess) return failure; wxInputStream *pStream = m_pProcess->GetInputStream(); if (!pStream) { wxMessageBox(_("Could not get reply from fzputtygen."), _("Command failed"), wxICON_EXCLAMATION); return failure; } char buffer[100]; wxString input; while (true) { int pos = input.Find('\n'); if (pos == wxNOT_FOUND) { pStream->Read(buffer, 99); int read; if (pStream->Eof() || !(read = pStream->LastRead())) { wxMessageBox(_("Could not get reply from fzputtygen."), _("Command failed"), wxICON_EXCLAMATION); return failure; } buffer[read] = 0; // Should only ever return ASCII strings so this is ok input += wxString(buffer, wxConvUTF8); pos = input.Find('\n'); if (pos == wxNOT_FOUND) continue; } int pos2; if (pos && input[pos - 1] == '\r') pos2 = pos - 1; else pos2 = pos; if (!pos2) { input = input.Mid(pos + 1); continue; } wxChar c = input[0]; reply = input.Mid(1, pos2 - 1); input = input.Mid(pos + 1); if (c == '0' || c == '1') return success; else if (c == '2') return error; // Ignore others } } bool COptionsPageConnectionSFTP::LoadKeyFile(wxString& keyFile, bool silent, wxString& comment, wxString& data) { if (!LoadProcess()) return false; // Get keytype if (!Send(_T("file " + keyFile))) return false; wxString reply; enum ReplyCode code = GetReply(reply); if (code == failure) return false; if (code == error || (reply != _T("0") && reply != _T("1"))) { if (!silent) { const wxString msg = wxString::Format(_("The file '%s' could not be loaded or does not contain a private key."), keyFile.c_str()); wxMessageBox(msg, _("Could not load keyfile"), wxICON_EXCLAMATION); } return false; } bool needs_conversion; if (reply == _T("1")) { if (silent) return false; needs_conversion = true; } else needs_conversion = false; // Check if file is encrypted if (!Send(_T("encrypted"))) return false; code = GetReply(reply); if (code != success) { wxASSERT(code != error); return false; } bool encrypted; if (reply == _T("1")) { if (silent) return false; encrypted = true; } else encrypted = false; if (encrypted || needs_conversion) { wxASSERT(!silent); wxString msg; if (needs_conversion) { if (!encrypted) msg = wxString::Format(_("The file '%s' is not in a format supported by FileZilla.\nWould you like to convert it into a supported format?"), keyFile.c_str()); else msg = wxString::Format(_("The file '%s' is not in a format supported by FileZilla.\nThe file is also password protected. Password protected keyfiles are not supported by FileZilla yet.\nWould you like to convert it into a supported, unprotected format?"), keyFile.c_str()); } else if (encrypted) msg = wxString::Format(_("The file '%s' is password protected. Password protected keyfiles are not supported by FileZilla yet.\nWould you like to convert it into an unprotected file?"), keyFile.c_str()); int res = wxMessageBox(msg, _("Convert keyfile"), wxICON_QUESTION | wxYES_NO); if (res != wxYES) return false; if (encrypted) { wxString msg = wxString::Format(_("Enter the password for the file '%s'.\nPlease note that the converted file will not be password protected."), keyFile.c_str()); CInputDialog dlg; if (!dlg.Create(this, _("Password required"), msg)) return false; dlg.SetPasswordMode(true); if (dlg.ShowModal() != wxID_OK) return false; if (!Send(_T("password " + dlg.GetValue()))) return false; if (GetReply(reply) != success) return false; } if (!Send(_T("load"))) return false; code = GetReply(reply); if (code == failure) return false; if (code != success) { wxString msg = wxString::Format(_("Failed to load private key: %s"), reply.c_str()); wxMessageBox(msg, _("Could not load private key"), wxICON_EXCLAMATION); return false; } wxFileDialog dlg(this, _("Select filename for converted keyfile"), _T(""), _T(""), _T("PuTTY private key files (*.ppk)|*.ppk"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dlg.ShowModal() != wxID_OK) return false; wxString newName = dlg.GetPath(); if (newName == _T("")) return false; if (newName == keyFile) { // Not actually a requirement by fzputtygen, but be on the safe side. We don't want the user to lose his keys. wxMessageBox(_("Source and target file may not be the same"), _("Could not convert private key"), wxICON_EXCLAMATION); return false; } Send(_T("write ") + newName); code = GetReply(reply); if (code == failure) return false; if (code != success) { wxMessageBox(wxString::Format(_("Could not write keyfile: %s"), reply.c_str()), _("Could not convert private key"), wxICON_EXCLAMATION); return false; } keyFile = newName; } else { if (!Send(_T("load"))) return false; code = GetReply(reply); if (code != success) return false; } Send(_T("comment")); code = GetReply(comment); if (code != success) return false; Send(_T("data")); code = GetReply(data); if (code != success) return false; return true; } void COptionsPageConnectionSFTP::OnEndProcess(wxProcessEvent& event) { delete m_pProcess; m_pProcess = 0; } void COptionsPageConnectionSFTP::OnAdd(wxCommandEvent& event) { wxFileDialog dlg(this, _("Select file containing private key"), _T(""), _T(""), wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dlg.ShowModal() != wxID_OK) return; const wxString file = dlg.GetPath(); AddKey(dlg.GetPath(), false); } void COptionsPageConnectionSFTP::OnRemove(wxCommandEvent& event) { wxListCtrl* pKeys = XRCCTRL(*this, "ID_KEYS", wxListCtrl); int index = pKeys->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (index == -1) return; pKeys->DeleteItem(index); } bool COptionsPageConnectionSFTP::AddKey(wxString keyFile, bool silent) { wxString comment, data; if (!LoadKeyFile(keyFile, silent, comment, data)) return false; if (KeyFileExists(keyFile)) { if (!silent) wxMessageBox(_("Selected file is already loaded"), _("Cannot load keyfile"), wxICON_INFORMATION); return false; } wxListCtrl* pKeys = XRCCTRL(*this, "ID_KEYS", wxListCtrl); int index = pKeys->InsertItem(pKeys->GetItemCount(), keyFile); pKeys->SetItem(index, 1, comment); pKeys->SetItem(index, 2, data); return true; } bool COptionsPageConnectionSFTP::KeyFileExists(const wxString& keyFile) { wxListCtrl* pKeys = XRCCTRL(*this, "ID_KEYS", wxListCtrl); for (int i = 0; i < pKeys->GetItemCount(); i++) { if (pKeys->GetItemText(i) == keyFile) return true; } return false; } void COptionsPageConnectionSFTP::SetCtrlState() { wxListCtrl* pKeys = XRCCTRL(*this, "ID_KEYS", wxListCtrl); int index = pKeys->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); XRCCTRL(*this, "ID_REMOVEKEY", wxButton)->Enable(index != -1); return; } void COptionsPageConnectionSFTP::OnSelChanged(wxListEvent& event) { SetCtrlState(); }