ImportSessions.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. //---------------------------------------------------------------------
  2. #include <vcl.h>
  3. #pragma hdrstop
  4. #include <Common.h>
  5. #include "ImportSessions.h"
  6. #include <Configuration.h>
  7. #include <CoreMain.h>
  8. #include <VCLCommon.h>
  9. #include <WinInterface.h>
  10. #include <TextsWin.h>
  11. #include <CoreMain.h>
  12. #include <Tools.h>
  13. #include <WinApi.h>
  14. #include <PasTools.hpp>
  15. //---------------------------------------------------------------------
  16. #pragma resource "*.dfm"
  17. //---------------------------------------------------------------------
  18. const int OpensshIndex = 3;
  19. const int KnownHostsIndex = 5;
  20. const int IniIndex = 4;
  21. //---------------------------------------------------------------------
  22. bool __fastcall DoImportSessionsDialog(TList * Imported)
  23. {
  24. std::unique_ptr<TImportSessionsDialog> ImportSessionsDialog(
  25. SafeFormCreate<TImportSessionsDialog>(Application));
  26. std::unique_ptr<TStrings> Errors(new TStringList());
  27. std::unique_ptr<TList> SessionListsList(new TList());
  28. UnicodeString Error;
  29. std::unique_ptr<TStoredSessionList> PuttyImportSessionList(
  30. GUIConfiguration->SelectPuttySessionsForImport(OriginalPuttyRegistryStorageKey, L"PuTTY", StoredSessions, Error));
  31. SessionListsList->Add(PuttyImportSessionList.get());
  32. Errors->Add(Error);
  33. std::unique_ptr<TStoredSessionList> KittyImportSessionList(
  34. GUIConfiguration->SelectPuttySessionsForImport(KittyRegistryStorageKey, L"KiTTY", StoredSessions, Error));
  35. SessionListsList->Add(KittyImportSessionList.get());
  36. Errors->Add(Error);
  37. std::unique_ptr<TStoredSessionList> FilezillaImportSessionList(
  38. Configuration->SelectFilezillaSessionsForImport(StoredSessions, Error));
  39. SessionListsList->Add(FilezillaImportSessionList.get());
  40. Errors->Add(Error);
  41. std::unique_ptr<TStoredSessionList> OpensshImportSessionList(
  42. Configuration->SelectOpensshSessionsForImport(StoredSessions, Error));
  43. SessionListsList->Add(OpensshImportSessionList.get());
  44. Errors->Add(Error);
  45. std::unique_ptr<TStoredSessionList> IniImportSessionList(ImportSessionsDialog->SelectSessionsForImport(Error));
  46. DebugAssert(IniIndex == SessionListsList->Count);
  47. SessionListsList->Add(IniImportSessionList.get());
  48. Errors->Add(Error);
  49. std::unique_ptr<TStoredSessionList> KnownHostsImportSessionList(
  50. Configuration->SelectKnownHostsSessionsForImport(StoredSessions, Error));
  51. DebugAssert(KnownHostsIndex == SessionListsList->Count);
  52. SessionListsList->Add(KnownHostsImportSessionList.get());
  53. Errors->Add(Error);
  54. DebugAssert(SessionListsList->Count == Errors->Count);
  55. ImportSessionsDialog->Init(SessionListsList.get(), Errors.get());
  56. bool Result = ImportSessionsDialog->Execute();
  57. if (Result)
  58. {
  59. // Particularly when importing known_hosts, there is no feedback.
  60. TInstantOperationVisualizer Visualizer;
  61. UnicodeString PuttyHostKeysSourceKey = OriginalPuttyRegistryStorageKey;
  62. TStoredSessionList * AIniImportSessionList =
  63. static_cast<TStoredSessionList *>(SessionListsList->Items[IniIndex]);
  64. TStoredSessionList * AKnownHostsImportSessionList =
  65. static_cast<TStoredSessionList *>(SessionListsList->Items[KnownHostsIndex]);
  66. StoredSessions->Import(PuttyImportSessionList.get(), true, Imported);
  67. TStoredSessionList::ImportHostKeys(PuttyHostKeysSourceKey, PuttyImportSessionList.get(), true);
  68. StoredSessions->Import(KittyImportSessionList.get(), true, Imported);
  69. TStoredSessionList::ImportHostKeys(KittyRegistryStorageKey, KittyImportSessionList.get(), true);
  70. StoredSessions->Import(FilezillaImportSessionList.get(), true, Imported);
  71. // FileZilla uses PuTTY's host key store
  72. TStoredSessionList::ImportHostKeys(PuttyHostKeysSourceKey, FilezillaImportSessionList.get(), true);
  73. StoredSessions->Import(OpensshImportSessionList.get(), true, Imported);
  74. if (StoredSessions->Import(AIniImportSessionList, true, Imported))
  75. {
  76. std::unique_ptr<THierarchicalStorage> IniStorage(TIniFileStorage::CreateFromPath(ImportSessionsDialog->IniFileName));
  77. TStoredSessionList::ImportHostKeys(IniStorage.get(), AIniImportSessionList, true);
  78. }
  79. // The actual import will be done by ImportSelectedKnownHosts
  80. TStoredSessionList::SelectKnownHostsForSelectedSessions(AKnownHostsImportSessionList, OpensshImportSessionList.get());
  81. TStoredSessionList::ImportSelectedKnownHosts(AKnownHostsImportSessionList);
  82. }
  83. return Result;
  84. }
  85. //---------------------------------------------------------------------
  86. __fastcall TImportSessionsDialog::TImportSessionsDialog(TComponent * AOwner) :
  87. TForm(AOwner)
  88. {
  89. UseSystemSettings(this);
  90. // this is loaded from res string to force translation
  91. Caption = LoadStr(IMPORT_CAPTION);
  92. }
  93. //---------------------------------------------------------------------
  94. void __fastcall TImportSessionsDialog::Init(TList * SessionListsList, TStrings * Errors)
  95. {
  96. FSessionListsList = SessionListsList;
  97. FErrors = Errors;
  98. for (int Index = 0; Index < SessionListsList->Count; Index++)
  99. {
  100. if ((SourceComboBox->ItemIndex < 0) && (GetSessionList(Index)->Count > 0))
  101. {
  102. SourceComboBox->ItemIndex = Index;
  103. }
  104. }
  105. if (SourceComboBox->ItemIndex < 0)
  106. {
  107. SourceComboBox->ItemIndex = 0;
  108. }
  109. int Offset = ScaleByTextHeight(this, 8);
  110. ErrorPanel->BoundsRect =
  111. TRect(
  112. SessionListView2->BoundsRect.Left + Offset, SessionListView2->BoundsRect.Top + Offset,
  113. SessionListView2->BoundsRect.Right - Offset, SessionListView2->BoundsRect.Bottom - Offset);
  114. }
  115. //---------------------------------------------------------------------
  116. TStoredSessionList * __fastcall TImportSessionsDialog::GetSessionList(int Index)
  117. {
  118. return reinterpret_cast<TStoredSessionList *>(FSessionListsList->Items[Index]);
  119. }
  120. //---------------------------------------------------------------------
  121. void __fastcall TImportSessionsDialog::UpdateControls()
  122. {
  123. BrowseButton->Visible = (SourceComboBox->ItemIndex == IniIndex);
  124. PasteButton->Visible = (SourceComboBox->ItemIndex == KnownHostsIndex);
  125. EnableControl(PasteButton, IsFormatInClipboard(CF_TEXT));
  126. EnableControl(OKButton, ListViewAnyChecked(SessionListView2));
  127. EnableControl(CheckAllButton, SessionListView2->Items->Count > 0);
  128. AutoSizeListColumnsWidth(SessionListView2);
  129. }
  130. //---------------------------------------------------------------------
  131. void __fastcall TImportSessionsDialog::ClearSelections()
  132. {
  133. for (int Index = 0; Index < SourceComboBox->Items->Count; Index++)
  134. {
  135. TStoredSessionList * SessionList = GetSessionList(Index);
  136. for (int Index = 0; Index < SessionList->Count; Index++)
  137. {
  138. SessionList->Sessions[Index]->Selected = false;
  139. }
  140. }
  141. }
  142. //---------------------------------------------------------------------
  143. TSessionData * TImportSessionsDialog::GetSessionData(TListItem * Item)
  144. {
  145. return DebugNotNull(static_cast<TSessionData *>(Item->Data));
  146. }
  147. //---------------------------------------------------------------------
  148. void __fastcall TImportSessionsDialog::SaveSelection()
  149. {
  150. for (int Index = 0; Index < SessionListView2->Items->Count; Index++)
  151. {
  152. TListItem * Item = SessionListView2->Items->Item[Index];
  153. GetSessionData(Item)->Selected = Item->Checked;
  154. }
  155. }
  156. //---------------------------------------------------------------------
  157. void __fastcall TImportSessionsDialog::LoadSessions()
  158. {
  159. TStoredSessionList * SessionList = GetSessionList(SourceComboBox->ItemIndex);
  160. SessionListView2->Items->BeginUpdate();
  161. try
  162. {
  163. SessionListView2->Items->Clear();
  164. for (int Index = 0; Index < SessionList->Count; Index++)
  165. {
  166. TSessionData * Session = SessionList->Sessions[Index];
  167. TListItem * Item = SessionListView2->Items->Add();
  168. Item->Data = Session;
  169. Item->Caption = Session->Name;
  170. Item->Checked = Session->Selected;
  171. }
  172. }
  173. __finally
  174. {
  175. SessionListView2->Items->EndUpdate();
  176. }
  177. UnicodeString Error = FErrors->Strings[SourceComboBox->ItemIndex];
  178. if ((SessionList->Count > 0) || Error.IsEmpty())
  179. {
  180. ErrorPanel->Visible = false;
  181. SessionListView2->TabStop = true;
  182. }
  183. else
  184. {
  185. ErrorLabel->Caption = Error;
  186. ErrorPanel->Visible = true;
  187. SessionListView2->TabStop = false;
  188. }
  189. UpdateControls();
  190. }
  191. //---------------------------------------------------------------------------
  192. void __fastcall TImportSessionsDialog::SessionListView2InfoTip(
  193. TObject * /*Sender*/, TListItem * Item, UnicodeString & InfoTip)
  194. {
  195. TSessionData * Data = GetSessionData(Item);
  196. if (SourceComboBox->ItemIndex == KnownHostsIndex)
  197. {
  198. UnicodeString Algs;
  199. UnicodeString HostKeys = Data->HostKey;
  200. while (!HostKeys.IsEmpty())
  201. {
  202. UnicodeString HostKey = CutToChar(HostKeys, L';', true);
  203. UnicodeString Alg = CutToChar(HostKey, L':', true);
  204. AddToList(Algs, Alg, L", ");
  205. }
  206. InfoTip = FMTLOAD(IMPORT_KNOWNHOSTS_INFO_TIP, (Data->HostName, Algs));
  207. }
  208. else
  209. {
  210. InfoTip = Data->InfoTip;
  211. }
  212. }
  213. //---------------------------------------------------------------------------
  214. void __fastcall TImportSessionsDialog::SessionListView2MouseDown(
  215. TObject * /*Sender*/, TMouseButton /*Button*/, TShiftState /*Shift*/,
  216. int /*X*/, int /*Y*/)
  217. {
  218. UpdateControls();
  219. }
  220. //---------------------------------------------------------------------------
  221. void __fastcall TImportSessionsDialog::SessionListView2KeyUp(
  222. TObject * /*Sender*/, WORD & /*Key*/, TShiftState /*Shift*/)
  223. {
  224. UpdateControls();
  225. }
  226. //---------------------------------------------------------------------------
  227. void __fastcall TImportSessionsDialog::FormShow(TObject * /*Sender*/)
  228. {
  229. // Load only now, as earlier loading somehow breaks SessionListView2 layout on initial per-monitor DPI scaling
  230. LoadSessions();
  231. }
  232. //---------------------------------------------------------------------------
  233. void __fastcall TImportSessionsDialog::CheckAllButtonClick(TObject * /*Sender*/)
  234. {
  235. ListViewCheckAll(SessionListView2, caToggle);
  236. UpdateControls();
  237. }
  238. //---------------------------------------------------------------------------
  239. void __fastcall TImportSessionsDialog::HelpButtonClick(TObject * /*Sender*/)
  240. {
  241. FormHelp(this);
  242. }
  243. //---------------------------------------------------------------------------
  244. bool TImportSessionsDialog::ConvertKeyFile(
  245. UnicodeString & KeyFile, TStrings * ConvertedKeyFiles, TStrings * NotConvertedKeyFiles)
  246. {
  247. bool ConvertedSession = false;
  248. if (!KeyFile.IsEmpty() &&
  249. FileExists(ApiPath(KeyFile)))
  250. {
  251. UnicodeString CanonicalPath = GetCanonicalPath(KeyFile);
  252. // Reuses the already converted keys saved under a custom name
  253. // (when saved under the default name they would be captured by the later condition based on GetConvertedKeyFileName)
  254. int CanonicalIndex = ConvertedKeyFiles->IndexOfName(CanonicalPath);
  255. if (CanonicalIndex >= 0)
  256. {
  257. KeyFile = ConvertedKeyFiles->ValueFromIndex[CanonicalIndex];
  258. ConvertedSession = true;
  259. }
  260. // Prevents asking about converting the same key again, when the user refuses the conversion.
  261. else if (NotConvertedKeyFiles->IndexOf(CanonicalPath) >= 0)
  262. {
  263. // noop
  264. }
  265. else
  266. {
  267. UnicodeString ConvertedFilename = GetConvertedKeyFileName(KeyFile);
  268. UnicodeString FileName;
  269. if (FileExists(ApiPath(ConvertedFilename)))
  270. {
  271. FileName = ConvertedFilename;
  272. ConvertedSession = true;
  273. }
  274. else
  275. {
  276. FileName = KeyFile;
  277. TDateTime TimestampBefore, TimestampAfter;
  278. FileAge(FileName, TimestampBefore);
  279. try
  280. {
  281. VerifyAndConvertKey(FileName, true);
  282. FileAge(FileName, TimestampAfter);
  283. if ((KeyFile != FileName) ||
  284. // should never happen as cancelling the saving throws EAbort
  285. DebugAlwaysTrue(TimestampBefore != TimestampAfter))
  286. {
  287. ConvertedSession = true;
  288. }
  289. }
  290. catch (EAbort &)
  291. {
  292. NotConvertedKeyFiles->Add(CanonicalPath);
  293. }
  294. }
  295. if (ConvertedSession)
  296. {
  297. KeyFile = FileName;
  298. ConvertedKeyFiles->Values[CanonicalPath] = FileName;
  299. }
  300. }
  301. }
  302. return ConvertedSession;
  303. }
  304. //---------------------------------------------------------------------------
  305. bool __fastcall TImportSessionsDialog::Execute()
  306. {
  307. bool Result = (ShowModal() == DefaultResult(this));
  308. if (Result)
  309. {
  310. ClearSelections();
  311. SaveSelection();
  312. if (SourceComboBox->ItemIndex == OpensshIndex)
  313. {
  314. std::unique_ptr<TStrings> ConvertedSessions(new TStringList());
  315. std::unique_ptr<TStrings> ConvertedKeyFiles(new TStringList());
  316. std::unique_ptr<TStrings> NotConvertedKeyFiles(CreateSortedStringList());
  317. for (int Index = 0; Index < SessionListView2->Items->Count; Index++)
  318. {
  319. TListItem * Item = SessionListView2->Items->Item[Index];
  320. if (Item->Checked)
  321. {
  322. TSessionData * Data = GetSessionData(Item);
  323. UnicodeString SessionKeys;
  324. UnicodeString PublicKeyFile = Data->PublicKeyFile;
  325. if (ConvertKeyFile(PublicKeyFile, ConvertedKeyFiles.get(), NotConvertedKeyFiles.get()))
  326. {
  327. Data->PublicKeyFile = PublicKeyFile;
  328. SessionKeys = PublicKeyFile;
  329. }
  330. UnicodeString TunnelPublicKeyFile = Data->TunnelPublicKeyFile;
  331. if (ConvertKeyFile(TunnelPublicKeyFile, ConvertedKeyFiles.get(), NotConvertedKeyFiles.get()))
  332. {
  333. Data->TunnelPublicKeyFile = TunnelPublicKeyFile;
  334. if (SessionKeys != TunnelPublicKeyFile)
  335. {
  336. AddToList(SessionKeys, TunnelPublicKeyFile, L", ");
  337. }
  338. }
  339. if (!SessionKeys.IsEmpty())
  340. {
  341. ConvertedSessions->Add(FORMAT(L"%s (%s)", (Data->Name, SessionKeys)));
  342. }
  343. }
  344. }
  345. if (ConvertedSessions->Count > 0)
  346. {
  347. UnicodeString Message = MainInstructions(FMTLOAD(IMPORT_CONVERTED_KEYS, (ConvertedKeyFiles->Count, ConvertedSessions->Count)));
  348. MoreMessageDialog(Message, ConvertedSessions.get(), qtInformation, qaOK, HelpKeyword);
  349. }
  350. }
  351. }
  352. return Result;
  353. }
  354. //---------------------------------------------------------------------------
  355. void __fastcall TImportSessionsDialog::SourceComboBoxSelect(TObject * /*Sender*/)
  356. {
  357. SaveSelection();
  358. LoadSessions();
  359. }
  360. //---------------------------------------------------------------------------
  361. void __fastcall TImportSessionsDialog::CreateHandle()
  362. {
  363. TForm::CreateHandle();
  364. if (DebugAlwaysTrue(HandleAllocated()))
  365. {
  366. HINSTANCE User32Library = LoadLibrary(L"user32.dll");
  367. AddClipboardFormatListenerProc AddClipboardFormatListener =
  368. (AddClipboardFormatListenerProc)GetProcAddress(User32Library, "AddClipboardFormatListener");
  369. if (AddClipboardFormatListener != NULL)
  370. {
  371. AddClipboardFormatListener(Handle);
  372. }
  373. }
  374. }
  375. //---------------------------------------------------------------------------
  376. void __fastcall TImportSessionsDialog::DestroyHandle()
  377. {
  378. if (DebugAlwaysTrue(HandleAllocated()))
  379. {
  380. HINSTANCE User32Library = LoadLibrary(L"user32.dll");
  381. RemoveClipboardFormatListenerProc RemoveClipboardFormatListener =
  382. (RemoveClipboardFormatListenerProc)GetProcAddress(User32Library, "RemoveClipboardFormatListener");
  383. if (RemoveClipboardFormatListener != NULL)
  384. {
  385. RemoveClipboardFormatListener(Handle);
  386. }
  387. }
  388. TForm::DestroyHandle();
  389. }
  390. //---------------------------------------------------------------------------
  391. void __fastcall TImportSessionsDialog::Dispatch(void * Message)
  392. {
  393. TMessage * M = static_cast<TMessage*>(Message);
  394. switch (M->Msg)
  395. {
  396. case WM_CLIPBOARDUPDATE:
  397. UpdateControls();
  398. TForm::Dispatch(Message);
  399. break;
  400. default:
  401. TForm::Dispatch(Message);
  402. break;
  403. }
  404. }
  405. //---------------------------------------------------------------------------
  406. void __fastcall TImportSessionsDialog::PasteButtonClick(TObject * /*Sender*/)
  407. {
  408. UnicodeString Text;
  409. // Proceed even when retrieving from clipboard fails, "no host keys" error will show.
  410. TextFromClipboard(Text, false);
  411. std::unique_ptr<TStrings> Lines(new TStringList());
  412. Lines->Text = Text;
  413. SessionListView2->Items->Clear();
  414. int Index = SourceComboBox->ItemIndex;
  415. UnicodeString Error;
  416. FPastedKnownHosts.reset(Configuration->SelectKnownHostsSessionsForImport(Lines.get(), StoredSessions, Error));
  417. FSessionListsList->Items[Index] = FPastedKnownHosts.get();
  418. FErrors->Strings[Index] = Error;
  419. LoadSessions();
  420. }
  421. //---------------------------------------------------------------------------
  422. TStoredSessionList * TImportSessionsDialog::SelectSessionsForImport(UnicodeString & Error)
  423. {
  424. return Configuration->SelectSessionsForImport(StoredSessions, FIniFileName, Error);
  425. }
  426. //---------------------------------------------------------------------------
  427. void __fastcall TImportSessionsDialog::BrowseButtonClick(TObject *)
  428. {
  429. std::unique_ptr<TOpenDialog> OpenDialog(new TOpenDialog(Application));
  430. OpenDialog->Title = LoadStr(IMPORT_INI_TITLE);
  431. OpenDialog->Filter = LoadStr(EXPORT_CONF_FILTER);
  432. OpenDialog->DefaultExt = L"ini";
  433. OpenDialog->FileName = DefaultStr(FIniFileName, Configuration->GetDefaultIniFileExportPath());
  434. if (OpenDialog->Execute())
  435. {
  436. SessionListView2->Items->Clear();
  437. int Index = SourceComboBox->ItemIndex;
  438. FIniFileName = OpenDialog->FileName;
  439. UnicodeString Error;
  440. FIniImportSessionList.reset(SelectSessionsForImport(Error));
  441. FSessionListsList->Items[Index] = FIniImportSessionList.get();
  442. FErrors->Strings[Index] = Error;
  443. LoadSessions();
  444. }
  445. }
  446. //---------------------------------------------------------------------------