ImportSessions.cpp 17 KB

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